home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1995 February: Tool Chest / Dev.CD Feb 95 / Dev.CD Feb 95.toast / Sample Code / Pascal Sample 3.0B10 / Source / TrafficLights.inc1.p < prev    next >
Encoding:
Text File  |  1993-10-13  |  86.2 KB  |  2,508 lines  |  [TEXT/MPS ]

  1. (******************************************************************************
  2. *
  3. *    Apple Macintosh Developer Technical Support
  4. *
  5. *    Code for the traffic lights
  6. *
  7. *    Program:    Sample 3.0
  8. *    File:        TrafficLights.inc1.p - Pascal implementation
  9. *
  10. *    by:            Matt Deatherage
  11. *
  12. *    Copyright © 1988-1993 Apple Computer, Inc.
  13. *    All rights reserved.
  14. *
  15. ******************************************************************************)
  16.  
  17. (*******************************************************************************
  18. * Global variables maintained by this unit that are private to routines in it
  19. *******************************************************************************)
  20.  
  21. VAR 
  22.  
  23.     gLastRectClicked: INTEGER;            { number of the last rectangle we clicked
  24.                                           in this window (used for double-click
  25.                                           testing) }
  26.     gLastWindowClicked: WindowPtr;         { pointer to the window we last clicked 
  27.                                           in (used for double-click testing) }
  28.     gLastClickedTime: LONGINT;             { time stamp of the last click (used for 
  29.                                           double-click testing) }
  30.  
  31. (*******************************************************************************
  32. * Routines in other files referenced by this code
  33. *******************************************************************************)
  34.  
  35. FUNCTION Print(thePrRecHdl: THPrint; theWindow: WindowPtr; thePict: PicHandle;
  36.                theMergeRec: THPrint): BOOLEAN;
  37.     EXTERNAL;
  38.  
  39. FUNCTION PageSetup(thePrRecHdl: THPrint): BOOLEAN;
  40.     EXTERNAL;
  41.  
  42. FUNCTION DoCircleOptions(VAR circle: CircleRec): BOOLEAN;
  43.     EXTERNAL;
  44.  
  45. {$S Main}
  46. (******************************************************************************
  47. *
  48. * Public: DoPageSetup
  49. *
  50. * This is a "shell" function, called by Sample.p so that it doesn't have
  51. * to know how to extract print records from our documents.  This quickly
  52. * extracts the print record and passes it to PageSetup in the PrintStuff unit.
  53. *
  54. ******************************************************************************)
  55.  
  56. FUNCTION DoPageSetup(theWindow: WindowPtr): BOOLEAN;
  57.  
  58. VAR
  59.     theDoc: DocumentPtr;        { the document for the window }
  60.  
  61. BEGIN
  62.     theDoc := DocumentPtr(GetWRefCon(theWindow));
  63.     DoPageSetup := PageSetup(theDoc^.printRecord);
  64. END; { DoPageSetup }
  65.  
  66. {$S Main}
  67. (******************************************************************************
  68. *
  69. * Public: DoPrint
  70. *
  71. * This is a "shell" function, called by Sample.p so that it doesn't have
  72. * to know how to extract print records from our documents.  This quickly
  73. * extracts the print record and passes it to Print in the PrintStuff unit.
  74. * We pass the window to draw instead, though I suppose we could just
  75. * call MakeDocumentPicture and pass a PICT Handle.
  76. *
  77. ******************************************************************************)
  78.  
  79. FUNCTION DoPrint(theWindow: WindowPtr): BOOLEAN;
  80.  
  81. VAR
  82.     theDoc: DocumentPtr;        { the document for the window }
  83.  
  84. BEGIN
  85.     theDoc := DocumentPtr(GetWRefCon(theWindow));
  86.     DoPrint := Print(theDoc^.printRecord, theWindow, NIL, NIL);
  87. END; { DoPrint }
  88.  
  89. {$S Main}
  90. (******************************************************************************
  91. *
  92. * private: DisposeDocument
  93. *
  94. * This routine closes a file associated with the document, if one is open,
  95. * and then releases all memory associated with the document.  It returns
  96. * FALSE if, for some reason the document couldn't be disposed.
  97. ******************************************************************************)
  98.  
  99. FUNCTION DisposeDocument(theDoc: DocumentPtr): BOOLEAN;
  100.  
  101. VAR
  102.     myErr: OSErr;                { any error from closing the file }
  103.  
  104. BEGIN
  105.     myErr := noErr;
  106.     IF theDoc^.ourFileRefNum <> 0 THEN
  107.         BEGIN
  108.             myErr := FSClose(theDoc^.ourFileRefNum);
  109.             IF myErr <> noErr THEN
  110.                 HandleFileError(myErr, theDoc^.ourFile.name);
  111.         END;
  112.     IF myErr = noErr THEN
  113.         BEGIN
  114.             DisposeHandle(Handle(theDoc^.printRecord));
  115.             DisposePtr(Ptr(theDoc));
  116.             DisposeDocument := TRUE;
  117.         END
  118.     ELSE
  119.         DisposeDocument := FALSE;
  120. END; { DisposeDocument }
  121.  
  122. {$S Main}
  123. (******************************************************************************
  124. *
  125. * Public: DoPrintFile
  126. *
  127. * This procedure gets a document from a reference disk file, prints it and
  128. * disposes of it.
  129. *
  130. ******************************************************************************)
  131.  
  132. PROCEDURE DoPrintFile(theFile: FileLikeSpecPtr; theMergePrintRecord: THPrint);
  133.  
  134. VAR
  135.     theDoc: DocumentPtr;        { the document we Create in memory }
  136.     ignore: BOOLEAN;            { ignored result of two routines we call }
  137.     thePict: PicHandle;            { the Picture we print }
  138.  
  139. BEGIN
  140.     theDoc := MakeEmptyDoc;
  141.     IF GetDocumentFromFile(theFile, theDoc) THEN
  142.         BEGIN
  143.             thePict := MakeDocumentPicture(theDoc);
  144.             ignore := Print(theDoc^.printRecord, NIL,
  145.                             thePict, theMergePrintRecord);
  146.             KillPicture(thePict);
  147.             ignore := DisposeDocument(theDoc);
  148.         END;
  149. END; { DoPrintFile }
  150.  
  151. {$S Main}
  152. (******************************************************************************
  153. *
  154. * Public: GetDocumentDirtyFlag
  155. *
  156. * A simple getter function -- returns a document structure's dirty flag.
  157. *
  158. ******************************************************************************)
  159.  
  160. FUNCTION GetDocumentDirtyFlag(theDoc: DocumentPtr): INTEGER;
  161.  
  162. BEGIN
  163.     GetDocumentDirtyFlag := theDoc^.dirtyFlag;
  164. END; { GetDocumentDirtyFlag }
  165.  
  166. {$S Main}
  167. (******************************************************************************
  168. *
  169. * Public: SetDocumentDirtyFlag
  170. *
  171. * A simple setter function -- sets a document structure's dirty flag.
  172. *
  173. ******************************************************************************)
  174.  
  175. PROCEDURE SetDocumentDirtyFlag(theDoc: DocumentPtr; theFlag: INTEGER);
  176.  
  177. BEGIN
  178.     theDoc^.dirtyFlag := theFlag;
  179. END; { SetDocumentDirtyFlag }
  180.  
  181. { GetDocumentDiskSize returns a LONGINT that says how many bytes our document takes }
  182. { as stored on disk. }
  183.  
  184. {$S Main}
  185. (******************************************************************************
  186. *
  187. * private: GetDocumentDiskSize
  188. *
  189. * This routine returns the size of a document structure on disk.  It's the
  190. * size of each CircleRec times the number of circles in the document, plus
  191. * a print record, plus four integers -- the circle count, the active circle,
  192. * the circle inset and the internal file version word that we use to see
  193. * if some later version of this program created this file.
  194. *
  195. * Note that if the file format changes, you have to change this routine,
  196. * PutDocumentToFile and GetDocumentFromFile.
  197. *
  198. ******************************************************************************)
  199.  
  200. FUNCTION GetDocumentDiskSize(theDoc: DocumentPtr): LONGINT;
  201.  
  202. BEGIN
  203.     GetDocumentDiskSize := (sizeof(CircleRec) * theDoc^.numCircles) +
  204.                            sizeof(TPrint) + (4 * sizeof(INTEGER));
  205. END; { GetDocumentDiskSize }
  206.  
  207. {$S Main}
  208. (******************************************************************************
  209. *
  210. * private: PutDocumentToFileGuts
  211. *
  212. * This routine takes a document structure pointer and writes the document
  213. * to a file on disk.  Even though each document contains a FileLikeSpec,
  214. * we check only the file reference number.  If it's zero, the file's never
  215. * been saved to disk, so we call the Standard file Package to find a place
  216. * to put the file.  We turn the working directory information in the SFReply
  217. * record into a real vRefNum and dirID, then use HOpen to open the file.
  218. * If that returns a file not found error, we try to Create the file and,
  219. * if that succeeds, try once again to open it.  Failure results in no
  220. * save and an Alert to the user.  For new files, we then include the 
  221. * application name 'STR ' resource, so the System 7 Finder can tell users 
  222. * what application they need if they don't have it.
  223. *
  224. * At that Point, we have a good file specification or the user canceled,
  225. * in which case we set myErr to userCanceledErr to indicate something
  226. * went wrong.
  227. *
  228. * The second half of the routine writes the document to disk, one piece
  229. * at a time.  If there's no error from the early part, we set the file
  230. * position to zero in preparation for writing (redundant if it's a new
  231. * file, not redundant if it's not).  Checking for errors after each step,
  232. * we then truncate the file to the size it will have just to be thorough.
  233. * Then we write the internal file version, the number of circles, the
  234. * active circle number and the circle inset.  Then follows the print
  235. * record, and then the array of circles.  After that, we flush the
  236. * file to make sure it's all written to disk properly.
  237. *
  238. * Note that if the file format changes, you have to change this routine,
  239. * GetDocumentDiskSize and GetDocumentFromFileGuts.
  240. *
  241. * This routine is a guts routine -- it's called from PutDocumentToFile
  242. * (below).  This routine returns any time it encounters an error, after
  243. * appropriate clean-up.  Not to do it this way requires so many levels
  244. * of "IF myErr = noErr THEN" that it becomes almost unreadable.
  245. ******************************************************************************)
  246.  
  247. PROCEDURE PutDocumentToFileGuts(theDoc: DocumentPtr; VAR myErr : OSErr);
  248.  
  249. VAR
  250.     where: Point;                    { location of Standard file dialog }
  251.     myReply: SFReply;                { contains user's Standard file choices }
  252.     theVRefNum: INTEGER;            { vRefNum of the volume we're writing to }
  253.     theDirID: LONGINT;                { directory ID of the file's parent dir }
  254.     procID: LONGINT;                { used in GetWDInfo; ignored by us }
  255.     fileVersion: INTEGER;            { variable to hold the file version const }
  256.     counter: INTEGER;                { loop counter variable }
  257.     myPB: ParamBlockRec;            { parameter block for the Flush call }
  258.     transferCount: LONGINT;            { how many bytes we wrote to disk }
  259.     ourResFileRefNum: INTEGER;        { resource file number of the disk file }
  260.     promptString: Str255;            { String to use as prompt in Std file box }
  261.  
  262. BEGIN
  263.  
  264.     myErr := noErr;             { start assuming no error }
  265.     WITH theDoc^ DO
  266.         BEGIN
  267.             IF ourFileRefNum = 0 THEN { is there an open file already? }
  268.                 BEGIN
  269.                     where.h := 50; { initialize our Point }
  270.                     where.v := 50;
  271.                     GetIndString(promptString, rMiscStrings, kSaveFileAs);
  272.                     SFPutFile(where, promptString, ourFile.name, NIL, myReply);
  273.                     IF NOT myReply.good THEN
  274.                         BEGIN
  275.                             myErr := userCanceledErr;
  276.                             Exit(PutDocumentToFileGuts);
  277.                         END;
  278.                         
  279.                     procID := 0;
  280.  
  281.                     { turn Standard file's working directory into a real vRefNum 
  282.                       and dirID }
  283.  
  284.                     myErr := GetWDInfo(myReply.vRefNum, theVRefNum, theDirID, procID);
  285.                     
  286.                     IF (myErr <> noErr) THEN
  287.                         Exit(PutDocumentToFileGuts);
  288.                         
  289.                     { open the file.  If we can't open it because it's not
  290.                       found, try to Create it and then try to open it again. }
  291.  
  292.                     myErr := HOpen(theVRefNum, theDirID, myReply.fName, fsRdWrPerm,
  293.                                    ourFileRefNum);
  294.                     IF myErr = fnfErr THEN { file not found? }
  295.                         BEGIN
  296.                             myErr := HCreate(theVRefNum, theDirID, myReply.fName, 
  297.                                               kOurCreatorType, kOurDocumentType);
  298.                             IF myErr = noErr THEN         { did we Create the file ok? }
  299.                                 myErr := HOpen(theVRefNum, theDirID, myReply.fName,
  300.                                                fsRdWrPerm, ourFileRefNum);
  301.                         END     { if file not found }
  302.                     ELSE
  303.                         Exit(PutDocumentToFileGuts);
  304.  
  305.                     { all is good; save info in our FileLikeSpec }
  306.  
  307.                     ourFile.name := myReply.fName;
  308.                     ourFile.parID := theDirID;
  309.                     ourFile.vRefNum := theVRefNum;
  310.  
  311.                     { add application name resource to the new file }
  312.  
  313.                     HCreateResFile(theVRefNum, theDirID, myReply.fName);
  314.                     ourResFileRefNum := HOpenResFile(theVRefNum, theDirID, 
  315.                                                      myReply.fName, fsRdWrPerm);
  316.                                                      { this shouldn't fail }
  317.                     myErr := ResError;
  318.                     IF myErr = noErr THEN
  319.                         myErr := DoCopyResource('STR ', kMissingAppNameStr, 
  320.                                                 gAppsResourceFile, ourResFileRefNum);
  321.                     CloseResFile(ourResFileRefNum);
  322.                 END;                            { if ourFileRefNum was zero }
  323.  
  324.             { we should have a good file open by this Point }
  325.         
  326.             myErr := SetFPos(ourFileRefNum, fsFromStart, 0); { Move to start of file }
  327.             IF myErr <> noErr THEN
  328.                 Exit(PutDocumentToFileGuts);
  329.         
  330.             myErr := SetEOF(ourFileRefNum, GetDocumentDiskSize(theDoc));
  331.             IF myErr <> noErr THEN
  332.                 Exit(PutDocumentToFileGuts);
  333.         
  334.             transferCount := sizeof(INTEGER);
  335.             fileVersion := kFileInternalVersion;
  336.             myErr := FSWrite(ourFileRefNum, transferCount, @fileVersion);
  337.             IF myErr <> noErr THEN
  338.                 Exit(PutDocumentToFileGuts);
  339.         
  340.             { transferCount := sizeof(INTEGER);   This is the same as before }
  341.             myErr := FSWrite(ourFileRefNum, transferCount, @numCircles);
  342.             IF myErr <> noErr THEN
  343.                 Exit(PutDocumentToFileGuts);
  344.         
  345.             { transferCount := sizeof(INTEGER);   This is the same as before }
  346.             myErr := FSWrite(ourFileRefNum, transferCount, @activeCircle);
  347.             IF myErr <> noErr THEN
  348.                 Exit(PutDocumentToFileGuts);
  349.         
  350.             { transferCount := sizeof(INTEGER);   This is the same as before }
  351.             myErr := FSWrite(ourFileRefNum, transferCount, @circleInset);
  352.             IF myErr <> noErr THEN
  353.                 Exit(PutDocumentToFileGuts);
  354.         
  355.             HLock(Handle(printRecord));
  356.             transferCount := sizeof(TPrint);
  357.             myErr := FSWrite(ourFileRefNum, transferCount, Ptr(printRecord^));
  358.             HUnlock(Handle(printRecord));
  359.             IF myErr <> noErr THEN
  360.                 Exit(PutDocumentToFileGuts);
  361.             
  362.             transferCount := sizeof(CircleRec);
  363.             FOR counter := 1 TO numCircles DO
  364.                 BEGIN
  365.                     myErr := FSWrite(ourFileRefNum, transferCount,
  366.                     @circleArray[counter]);
  367.                     IF myErr <> noErr THEN
  368.                         counter := numCircles;
  369.                 END;                             { loop to write circle array }
  370.             IF myErr <> noErr THEN
  371.                 Exit(PutDocumentToFileGuts);
  372.         
  373.             myPB.ioCompletion := NIL;
  374.             myPB.ioRefNum := ourFileRefNum;
  375.             myPB.ioResult := noErr;
  376.             myErr := PBFlushFile(@myPB, FALSE); { synchronous flush call }
  377.             
  378.             IF myErr <> noErr THEN
  379.                 Exit(PutDocumentToFileGuts);
  380.         
  381.             SetDocumentDirtyFlag(theDoc, kDocumentClean);
  382.  
  383.         END; {with theDoc^ do begin }
  384.         
  385. END; { PutDocumentToFileGuts }
  386.  
  387. {$S Main}
  388. (******************************************************************************
  389. *
  390. * private: PutDocumentToFile
  391. *
  392. * This routine is a shell function.  It saves the existing file information
  393. * in the document and calls PutDocumentToFileGuts, which returns whenever
  394. * it finds an error.  If it returns with no error, the document was correctly
  395. * written to disk.
  396. ******************************************************************************)
  397.  
  398. FUNCTION PutDocumentToFile(theDoc: DocumentPtr): BOOLEAN;
  399.  
  400. VAR
  401.     oldFileLikeSpec: FileLikeSpec;    { copy of old file information in case
  402.                                       there's an error and we need it back }
  403.     oldFileRefNum: INTEGER;            { copy of old file refNum -- same reason }
  404.     myErr,                            { error for PutDocumentToFileGuts }
  405.     ignore: OSErr;                    { error we ignore when we can't use myErr }
  406.     
  407. BEGIN
  408.     myErr := noErr;                    { assume nothing goes wrong }
  409.     WITH theDoc^ DO
  410.         BEGIN
  411.             oldFileLikeSpec := ourFile;            { save old file information }
  412.             oldFileRefNum := ourFileRefNum;        { save old reference number }
  413.             PutDocumentToFileGuts(theDoc, myErr);         { write it to disk }
  414.             IF (myErr <> noErr) THEN
  415.                 BEGIN
  416.                     ignore := FSClose(ourFileRefNum);
  417.                     ourFileRefNum := oldFileRefNum;
  418.                     ourFile := oldFileLikeSpec;
  419.                     HandleFileError(myErr, ourFile.name);
  420.                 END;
  421.         END;
  422.         
  423.     PutDocumentToFile := (myErr = noErr);
  424. END; { PutDocumentToFile }
  425.  
  426. {$S Main}
  427. (******************************************************************************
  428. *
  429. * Public: InvalidateCircle
  430. *
  431. * This routine is an accessor function; it invalidates a CircleRec's rectangle
  432. * in the current GrafPort.
  433. ******************************************************************************)
  434.  
  435. PROCEDURE InvalidateCircle(theCircle: CircleRecPtr);
  436.  
  437. BEGIN
  438.     InvalRect(theCircle^.circleRect);
  439. END; { InvalidateCircle }
  440.  
  441. {$S Main}
  442. (******************************************************************************
  443. *
  444. * private: ChangeActiveCircle
  445. *
  446. * This procedure changes a document's currently active circle to the one
  447. * passed in newActive, invalidating both the old and the new circles if
  448. * the active circle actually changed.
  449. *
  450. ******************************************************************************)
  451.  
  452. PROCEDURE ChangeActiveCircle(newActive: INTEGER; theDoc: DocumentPtr);
  453.  
  454. BEGIN
  455.     WITH theDoc^ DO
  456.         IF activeCircle <> newActive THEN
  457.             BEGIN
  458.                 InvalidateCircle(@circleArray[activeCircle]);
  459.                 activeCircle := newActive;
  460.                 InvalidateCircle(@circleArray[activeCircle]);
  461.             END; { if }
  462. END; { ChangeActiveCircle }
  463.  
  464. {$S Main}
  465. (******************************************************************************
  466. *
  467. * private: MakeDefaultCircle
  468. *
  469. * This routine takes a pointer to a circle record and a number saying which
  470. * circle in a document this one is supposed to be.  The routine goes to
  471. * our resource fork and gets the default font, color, size and text for that
  472. * circle number and creates the circle to meet those specifications.
  473. *
  474. ******************************************************************************)
  475.  
  476. PROCEDURE MakeDefaultCircle(circleNum: INTEGER; circleRecToFill: CircleRecPtr);
  477.  
  478. VAR
  479.     colorResHandle: Handle;            { Handle to the resource with our color }
  480.  
  481. BEGIN
  482.     WITH circleRecToFill^ DO
  483.         BEGIN
  484.             GetIndString(circleFont, rDefaultFonts, circleNum);
  485.  
  486.             { This Line is semi-tricky.  It says "Get the resource of type
  487.               'DfSz' with ID rDefaultSizes, dereference it once to get a
  488.               pointer, pretend that's a pointer to an INTEGER, and then
  489.               dereference _that_ pointer to get a real INTEGER.  Assign
  490.               that INTEGER to circleTxSize.
  491.               
  492.               And they say C is hard to understand.  You can obscure any
  493.               language if you try hard enough.  C just makes it easier.  }
  494.             
  495.             circleTxSize := IntegerPtr(GetResource('DfSz', rDefaultSizes)^)^;
  496.             GetIndString(circleText, rDefaultStrings, circleNum);
  497.             colorResHandle := GetResource('rgb ', rDefaultColorID + circleNum);
  498.             IF colorResHandle = NIL THEN
  499.                 colorResHandle := GetResource('rgb ', rDefaultColorID); { this
  500.                 one is required }
  501.             BlockMove(colorResHandle^, Ptr(@circleColor), sizeof(RGBColor));
  502.             circleFace := [];        { Empty style; there are no default style
  503.                                       resources }
  504.         END; { WITH circleRecToFill DO BEGIN }
  505. END; { MakeDefaultCircle}
  506.  
  507. {$S Main}
  508. (******************************************************************************
  509. *
  510. * private: GetCircleRectSize
  511. *
  512. * This routine takes a document and returns the size of a circle's rectangle
  513. * and inset in that document in two var parameters -- rectSize and insetSize.
  514. * If usePrefs is TRUE, the values come from the preferences and theDoc is
  515. * ignored.
  516. *
  517. * This routine assumes that all circles in a document have the same size
  518. * and inset as the first one, which is TRUE for our files.  just noting it.
  519. *
  520. ******************************************************************************)
  521.  
  522. PROCEDURE GetCircleRectSize(theDoc: DocumentPtr; VAR rectSize: INTEGER;
  523.                             VAR insetSize: INTEGER; usePrefs: BOOLEAN);
  524.  
  525. BEGIN
  526.     IF usePrefs THEN
  527.         BEGIN
  528.             rectSize := gPrefsRecord.circleRectSize;
  529.             insetSize := gPrefsRecord.circleInsetSize;
  530.         END
  531.     ELSE
  532.         BEGIN
  533.             insetSize := theDoc^.circleInset;
  534.             WITH theDoc^.circleArray[1].circleRect DO
  535.                 rectSize := (right - left) + (2 * insetSize);
  536.         END;
  537. END; { GetCircleRectSize }
  538.  
  539. {$S Main}
  540. (******************************************************************************
  541. *
  542. * private: AddCircle
  543. *
  544. * This routine takes a document and a circle, and adds the circle to the
  545. * document.  The circle becomes the last one in the document.  If makeActive
  546. * is TRUE, the new circle is made the active circle.
  547. *
  548. ******************************************************************************)
  549.  
  550. PROCEDURE AddCircle(theDoc: DocumentPtr; theCircleToAdd: CircleRecPtr;
  551.                     makeActive: BOOLEAN);
  552.  
  553. VAR
  554.     nextCircle: INTEGER;            { what circle are we adding? }
  555.     circleTop: INTEGER;                { Y position of the new circle's top }
  556.     rectSize: INTEGER;                { size of the circle's rectangle }
  557.     insetSize: INTEGER;                { the circle's inset value }
  558.  
  559. BEGIN
  560.  
  561.     nextCircle := theDoc^.numCircles + 1;    { Circle to add }
  562.     IF nextCircle <= gPrefsRecord.maxNumCircles THEN
  563.         BEGIN
  564.             theDoc^.numCircles := nextCircle;
  565.             theDoc^.circleArray[nextCircle] := theCircleToAdd^;
  566.     
  567.             { Now that we've copied the circle, adjust the rectangle 
  568.               to the right size }
  569.  
  570.             GetCircleRectSize(theDoc, rectSize, insetSize,
  571.                               (nextCircle = 1));
  572.  
  573.             { The top of the next circle is the the height of all the other
  574.               rectangles (squares) times the number of existing circles }
  575.             
  576.             circleTop := (nextCircle - 1) * rectSize; { do this calculation only
  577.                                                         once }
  578.             SetDocumentDirtyFlag(theDoc, kDocumentDirty);
  579.  
  580.             WITH theDoc^.circleArray[nextCircle] DO
  581.                 BEGIN
  582.                     SetRect(circleRect, 0, circleTop, rectSize, circleTop +
  583.                             rectSize);
  584.                     InsetRect(circleRect, insetSize, insetSize);
  585.                 END; { WITH theDoc^.circleArray[nextCircle] DO BEGIN }
  586.  
  587.             IF makeActive THEN
  588.                 ChangeActiveCircle(nextCircle, theDoc);
  589.  
  590.         END; {if nextCircle <= gPrefsRecord.maxNumCircles }
  591. END; { AddCircle }
  592.  
  593. {$S Main}
  594. (******************************************************************************
  595. *
  596. * Public: GetDocumentDrawingSize
  597. *
  598. * This routine returns the size of a document, in pixels (as a Point
  599. * structure).  Like GetCircleRectSize, it requires changing if all circles
  600. * in a document aren't the same size as the first one.
  601. *
  602. ******************************************************************************)
  603.  
  604. PROCEDURE GetDocumentDrawingSize(theDoc: DocumentPtr; VAR theSize: Point);
  605.  
  606. VAR
  607.     rectSize,                    { size of each circle's rectangle }
  608.     insetSize: INTEGER;            { inset for each circle from its Rect }
  609.  
  610. BEGIN
  611.  
  612.     GetCircleRectSize(theDoc, rectSize, insetSize, 
  613.                       (theDoc^.numCircles < 1));    { use prefs if no circles
  614.                                                           in document }
  615.     theSize.v := theDoc^.numCircles * rectSize;
  616.     theSize.h := rectSize;
  617. END; { GetDocumentDrawingSize }
  618.  
  619. {$S Main}
  620. (******************************************************************************
  621. *
  622. * private: AdjustWindowSize
  623. *
  624. * AdjustWindowSize takes a window and a DocumentPtr and makes the window
  625. * just the right size to draw the document in.
  626. *
  627. ******************************************************************************)
  628.  
  629. PROCEDURE AdjustWindowSize(window: WindowPtr; theDoc: DocumentPtr);
  630.  
  631. VAR
  632.     theSize: Point;                { size of doc as returned by our routine
  633.                                   GetDocumentDrawingSize }
  634.  
  635. BEGIN
  636.     GetDocumentDrawingSize(theDoc, theSize);
  637.     SizeWindow(window, theSize.h, theSize.v, TRUE);
  638. END; { AdjustWindowSize }
  639.  
  640. {$S Main}
  641. (******************************************************************************
  642. *
  643. * Public: AddDefaultCircle
  644. *
  645. * This routine adds the "next" circle as the default circle to a document
  646. * and, optionally, its window.  If there's a window pointer, that window
  647. * is resized to fit the new document.
  648. *
  649. ******************************************************************************)
  650.  
  651. PROCEDURE AddDefaultCircle(theDoc: DocumentPtr; theWindow: WindowPtr);
  652.  
  653. VAR
  654.     ourCircle: CircleRec;            { the circle we add }
  655.  
  656. BEGIN
  657.     MakeDefaultCircle(theDoc^.numCircles + 1, @ourCircle);
  658.     AddCircle(theDoc, @ourCircle, FALSE);
  659.     IF theWindow <> NIL THEN
  660.         AdjustWindowSize(theWindow, theDoc);
  661.     SetDocumentDirtyFlag(theDoc, kDocumentDirty);
  662. END; { AddDefaultCircle }
  663.  
  664. {$S Main}
  665. (******************************************************************************
  666. *
  667. * Public: DeleteActiveCircle
  668. *
  669. * This routine deletes the active circle from a window's document.  You should
  670. * not call this if there's only one circle in the document, but it does
  671. * watch for that to avoid problems elsewhere.  The document is marked dirty.
  672. *
  673. * If the circle is not the last one, all the circles below it are upshifted
  674. * so there are no gaps, and the window is resized and completely redrawn.
  675. *
  676. ******************************************************************************)
  677.  
  678. PROCEDURE DeleteActiveCircle(theWindow: WindowPtr);
  679.  
  680. VAR
  681.     oldActive,                    { the active circle before we delete }
  682.     oldNumber,                     { the number of circles before we delete }
  683.     counter,                    { loop counter variable }
  684.     rectSize,                     { size of each circle's rectangle }
  685.     insetSize: INTEGER;            { inset value for each circle, ignored }
  686.     theDoc: DocumentPtr;        { the window's associated document }
  687.  
  688. BEGIN
  689.     theDoc := DocumentPtr(GetWRefCon(theWindow));
  690.     WITH theDoc^ DO
  691.         BEGIN
  692.             oldActive := activeCircle;
  693.             oldNumber := numCircles;
  694.             numCircles := numCircles - 1;
  695.             GetCircleRectSize(theDoc, rectSize, insetSize, FALSE);
  696.             FOR counter := oldActive + 1 TO oldNumber DO
  697.                 BEGIN
  698.                     circleArray[counter - 1] := circleArray[counter];
  699.                     OffsetRect(circleArray[counter - 1].circleRect, 0,
  700.                                -rectSize);
  701.                 END;
  702.             activeCircle := activeCircle - 1;
  703.             IF activeCircle < 1 THEN
  704.                 activeCircle := 1;
  705.             SetDocumentDirtyFlag(theDoc, kDocumentDirty);
  706.             AdjustWindowSize(theWindow, theDoc);
  707.             InvalRect(theWindow^.portRect);
  708.         END; { with }
  709. END; { DeleteActiveCircle }
  710.  
  711. {$S Main}
  712. (******************************************************************************
  713. *
  714. * private: FindWindowByFileSpec
  715. *
  716. * This would be in SampleUtilities if it didn't have specific knowledge of our
  717. * document structure, because it's kind of handy.
  718. *
  719. * FindWindowByFileSpec takes a FileLikeSpec and calls PBGetCatInfo on it,
  720. * synchronously, to find the file's reference number.  It then walks through
  721. * all windows, looking in each application window's associated document
  722. * structure for a document sharing the same file number.  If it finds one,
  723. * it returns that window's pointer.
  724. *
  725. * This is used by our "open" routine so that if you open a file that's
  726. * already open, the window is selected and brought to the front instead of
  727. * generating a "That file is already open" error.
  728. *
  729. ******************************************************************************)
  730.  
  731. FUNCTION FindWindowByFileSpec(theFile: FileLikeSpecPtr): WindowPtr;
  732.  
  733. VAR
  734.     ourPB: CInfoPBRec;                { parameter block for PBGetCatInfo }
  735.     myErr: OSErr;                    { error code from PBGetCatInfo }
  736.     aWindow: WindowPtr;                { the window we're examining }
  737.     theRefNum: INTEGER;                { the passed file's reference number }
  738.     foundIt: BOOLEAN;                { did we find the window? }
  739.  
  740. BEGIN
  741.     foundIt := FALSE;                    { assume no window matches }
  742.     WITH ourPB DO
  743.         BEGIN
  744.             ioNamePtr := @theFile^.name;        { set up our PB: file name }
  745.             ioVRefNum := theFile^.vRefNum;        { copy the vRefNum }
  746.             ioFDirIndex := 0;                    { this should be zero }
  747.             ioDirID := theFile^.parID            { the directory ID }
  748.         END;
  749.     myErr := PBGetCatInfo(@ourPB, FALSE);
  750.  
  751.     IF myErr = noErr THEN
  752.         BEGIN
  753.             theRefNum := ourPB.ioFRefNum;        { copy the refNum }
  754.             aWindow := FrontWindow;                { start with the front window }
  755.             
  756.             WHILE (aWindow <> NIL) AND NOT foundIt DO
  757.                 BEGIN
  758.                     IF IsAppWindow(aWindow) THEN
  759.                         IF DocumentPtr(GetWRefCon(aWindow))^.ourFileRefNum =
  760.                                theRefNum THEN
  761.                                 foundIt := TRUE;
  762.                     IF NOT foundIt THEN
  763.                         aWindow := GetNextWindow(aWindow);
  764.                 END; { WHILE }
  765.         END; { IF myErr = noErr }
  766.                         
  767.     IF foundIt THEN
  768.         FindWindowByFileSpec := aWindow
  769.     ELSE
  770.         FindWindowByFileSpec := NIL;
  771.  
  772. END; { FindWindowByFileSpec }
  773.  
  774. {$S Main}
  775. (******************************************************************************
  776. *
  777. * private: GetDocumentFromFileGuts
  778. *
  779. * This routine fills in a document pointed to by theDoc and fills it in with
  780. * the contents of the file referenced.  If theFile is NIL, we call the
  781. * Standard file Package to get a file to open.  Like PutDocumentToFile,
  782. * the first thing we do is turn the working directory information in the
  783. * SFReply record into a real vRefNum and dirID, and fill in a new FileLikeSpec
  784. * with the information.
  785. *
  786. * Then, we try to open the file, assuming no errors and no user cancellations.
  787. * If HOpen says the disk is write-protected, or the file is locked, or the
  788. * volume is locked, or there is no write permission, or maybe just an
  789. * AppleShare-type access privileges error, then we try to open the file
  790. * read-only.  If that works, either the file is already open by us or
  791. * we just can't open it with write permission.  If it's the former, we just
  792. * select the window.  If it's the latter, we open it read-only and Alert
  793. * the user that he can't save changes to this file.  (If he tries, he'll
  794. * get an error.  There's no place in the document structure to say this is
  795. * a read-only document, though that might be a nice thing to do.)
  796. *
  797. * At that Point, we have a good file specification or the user canceled,
  798. * in which case we set myErr to userCanceledErr to indicate something
  799. * went wrong.
  800. *
  801. * The second half of the routine reads the document from disk, one piece
  802. * at a time.  If there's no error from the early part, we set the file
  803. * position to zero in preparation for reading (redundant if it's a new
  804. * file, not redundant if it's not).  Checking for errors after each step,
  805. * we then read the internal file version, the number of circles, the
  806. * active circle number and the circle inset.  Then follows the print
  807. * record, which is validated to make sure it's still good for the current
  808. * printer driver.  Then comes the array of circles.  After that, if it all
  809. * worked, we set our "success" flag to indicate such.
  810. *
  811. * Note that if the file format changes, you have to change this routine,
  812. * GetDocumentDiskSize and PutDocumentToFileGuts.
  813. *
  814. * when we back out of all the error checking, we look to see if the file
  815. * is a stationery file.  If it is, we close the file, change the window
  816. * title and file name to the next "untitled" String, mark the document
  817. * as dirty and as unsaved by zeroing the file reference number.
  818. *
  819. * The whole routine returns noErr in the VAR myError if everything was
  820. * fine.
  821. *
  822. * This routine is a guts routine -- it's called from GetDocumentFromFile
  823. * (below).  This routine returns any time it encounters an error, after
  824. * appropriate clean-up.  Not to do it this way requires so many levels
  825. * of "IF myErr = noErr THEN" that it becomes almost unreadable.
  826. ******************************************************************************)
  827.  
  828. PROCEDURE GetDocumentFromFileGuts(theFile: FileLikeSpecPtr; theDoc: DocumentPtr;
  829.                                     VAR myErr : OSErr);
  830.  
  831. VAR
  832.     where: Point;                    { location for SFGetFile dialog }
  833.     procID: LONGINT;                { unused, required by GetWDInfo }
  834.     theVRefNum: INTEGER;            { real vRefNum for SFGetFile result }
  835.     theDirID: LONGINT;                { real dirID for SFGetFile result }
  836.     myTypeList: SFTypeList;            { the typeList for SFGetFile filtering }
  837.     transferCount: LONGINT;            { count of bytes read from disk }
  838.     fileVersion: INTEGER;            { variable to hold our file version constant }
  839.     ignore,                            { throw-away result of PrValidate }
  840.     isStationeryDoc: BOOLEAN;        { is this file stationery? }
  841.     counter: INTEGER;                { loop variable counter }
  842.     theRefNum: INTEGER;                { reference number of the file we open }
  843.     ourFInfo: FInfo;                { for checking the isStationery bit }
  844.     newFileSpec: FileLikeSpec;        { holder for the file we're opening's specs }
  845.     myReply: SFReply;                { the user's SFGetFile choice }
  846.     theWindow: WindowPtr;            { for the already-open window, if there is one }
  847.  
  848. BEGIN
  849.  
  850.     IF theFile = NIL THEN
  851.         BEGIN
  852.             where.h := 50;
  853.             where.v := 50;
  854.             myTypeList[0] := kOurDocumentType;
  855.             SFGetFile(where, '', NIL, 1, myTypeList, NIL, myReply);
  856.             IF NOT myReply.good THEN BEGIN
  857.                 myErr := userCanceledErr;
  858.                 Exit(GetDocumentFromFileGuts);
  859.             END;
  860.                 
  861.             procID := 0;
  862.             myErr := GetWDInfo(myReply.vRefNum, theVRefNum, theDirID, procID);
  863.             
  864.             { If they passed NIL for theFile, we can't fill in a NIL pointer 
  865.               with a new FileLikeSpec, so we use a local variable for the rest 
  866.               of the routine, just in case }
  867.             
  868.             IF myErr <> noErr THEN
  869.                 Exit(GetDocumentFromFileGuts);
  870.  
  871.             newFileSpec.vRefNum := theVRefNum;
  872.             newFileSpec.parID := theDirID;
  873.             newFileSpec.name := myReply.fName;
  874.         END
  875.     ELSE
  876.         newFileSpec := theFile^;        { copy the passed spec into our variable }
  877.  
  878.     WITH newFileSpec DO
  879.         BEGIN
  880.  
  881.             { Is this a stationery file?  If so, we'll eventually close 
  882.               the file on disk and treat it as an "untitled" document 
  883.               with initial content that's dirty.  We want to know now,
  884.               though, so we can avoid warning users that stationery is
  885.               locked (like they care). }
  886.             
  887.             myErr := HGetFInfo(vRefNum, parID, name, ourFInfo);
  888.             isStationeryDoc := FALSE;
  889.             IF myErr <> noErr THEN
  890.                 Exit(GetDocumentFromFileGuts);
  891.                 
  892.             isStationeryDoc := (BAND(ourFInfo.fdFlags, kIsStationary) <> 0);
  893.                                             { yes, _I_ know it's misspelled... }
  894.  
  895.             myErr := HOpen(vRefNum, parID, name, fsRdWrPerm, theRefNum);
  896.             
  897.             IF myErr <> noErr THEN
  898.                 BEGIN
  899.                     
  900.                     { Try to open read-only, and if we can, see if we already
  901.                       have the file open. }
  902.                     
  903.                     myErr := HOpen(vRefNum, parID, name, fsRdPerm, theRefNum);
  904.                     
  905.                     { If we can't open it read-only, either, we're sunk. }
  906.                     
  907.                     IF myErr <> noErr THEN
  908.                         Exit(GetDocumentFromFileGuts);
  909.                     
  910.                     theWindow := FindWindowByFileSpec(@newFileSpec);
  911.                     IF ((theWindow = NIL) AND (NOT isStationeryDoc)) THEN
  912.                         BEGIN
  913.                             ParamText('', name, '', '');
  914.                             AlertUser(rFileDiskLocked);
  915.                         END
  916.                     ELSE IF (NOT isStationeryDoc) THEN
  917.                         BEGIN
  918.                             myErr := FSClose(theRefNum);
  919.                             SelectWindow(theWindow);
  920.                             myErr := userCanceledErr;
  921.                             Exit(GetDocumentFromFileGuts);
  922.                         END;
  923.                 END;                { IF myErr <> noErr }
  924.         END;                         { WITH }
  925.  
  926.     WITH theDoc^ DO
  927.         BEGIN         { here's where we use the reference number and do our thing }
  928.             myErr := SetFPos(theRefNum, fsFromStart, 0); { Move to start of file }
  929.             IF myErr <> noErr THEN
  930.                 Exit(GetDocumentFromFileGuts);
  931.  
  932.             transferCount := sizeof(INTEGER);
  933.             myErr := FSRead(theRefNum, transferCount, @fileVersion);
  934.             IF myErr <> noErr THEN
  935.                 Exit(GetDocumentFromFileGuts);
  936.                 
  937.             IF fileVersion <> kFileInternalVersion THEN
  938.                 BEGIN
  939.                     AlertUser(rFileVersionInconsistent);
  940.                     myErr := noErr;
  941.                     Exit(GetDocumentFromFileGuts);
  942.                 END;
  943.             
  944.             { transferCount := sizeof(INTEGER);   This is the same as before }
  945.             myErr := FSRead(theRefNum, transferCount, @numCircles);
  946.             IF myErr <> noErr THEN
  947.                 Exit(GetDocumentFromFileGuts);
  948.                 
  949.             { transferCount := sizeof(INTEGER);   This is the same as before }
  950.             myErr := FSRead(theRefNum, transferCount, @activeCircle);
  951.             IF myErr <> noErr THEN
  952.                 Exit(GetDocumentFromFileGuts);
  953.                 
  954.             { transferCount := sizeof(INTEGER);   This is the same as before }
  955.             myErr := FSRead(theRefNum, transferCount, @circleInset);
  956.             IF myErr <> noErr THEN
  957.                 Exit(GetDocumentFromFileGuts);
  958.                 
  959.             { Lock the print record Handle before reading into it, and call
  960.               PrValidate on it when we have it. }
  961.             
  962.             HLock(Handle(printRecord));
  963.             transferCount := sizeof(TPrint);
  964.             myErr := FSRead(theRefNum, transferCount, Ptr(printRecord^));
  965.             IF myErr <> noErr THEN
  966.                 Exit(GetDocumentFromFileGuts);
  967.                 
  968.             HUnlock(Handle(printRecord));
  969.             PrOpen;
  970.             ignore := PrValidate(printRecord);
  971.             PrClose;
  972.  
  973.             transferCount := sizeof(CircleRec);
  974.             FOR counter := 1 TO numCircles DO
  975.                 BEGIN
  976.                     myErr := FSRead(theRefNum, transferCount, @circleArray[counter]);
  977.                     IF myErr <> noErr THEN
  978.                         counter := numCircles;
  979.                 END; { loop to read circle array }
  980.                 
  981.             IF myErr <> noErr THEN
  982.                 Exit(GetDocumentFromFileGuts);
  983.                 
  984.             ourFile := newFileSpec;                    { fill in our FileLikeSpec }
  985.  
  986.             { Now, if it's stationery, close the old file and pretend like it's
  987.               a "new" document but with some initial content }
  988.             
  989.             IF isStationeryDoc THEN
  990.                 BEGIN
  991.                     ourFile.vRefNum := 0;
  992.                     ourFile.parID := 0;
  993.                     myErr := FSClose(theRefNum);
  994.                     ourFileRefNum := 0;
  995.                     CreateWindowTitle(ourFile.name);
  996.                     SetDocumentDirtyFlag(theDoc, kDocumentNew);
  997.                                             { pretend this is a new document! }
  998.                 END
  999.             ELSE
  1000.                 BEGIN
  1001.                     ourFileRefNum := theRefNum;
  1002.                     SetDocumentDirtyFlag(theDoc, kDocumentClean);
  1003.                 END;
  1004.                 
  1005.         END;        
  1006.  
  1007. END; { GetDocumentFromFileGuts }
  1008.  
  1009. {$S Main}
  1010. (******************************************************************************
  1011. *
  1012. * Public: GetDocumentFromFile
  1013. *
  1014. * This is a shell routine; it calls GetDocumentFromFileGuts, which returns
  1015. * if it encounters any errors.  This routine returns TRUE to the caller
  1016. * if the document was put to file without errors.  The description of all
  1017. * the mechanics is above in GetDocumentFromFileGuts.
  1018. *
  1019. ******************************************************************************)
  1020.  
  1021. FUNCTION GetDocumentFromFile(theFile: FileLikeSpecPtr; theDoc: DocumentPtr):
  1022.                             BOOLEAN;
  1023.                             
  1024. VAR
  1025.     myErr: OSErr;                        { for GetDocumentFromFileGuts }
  1026.  
  1027. BEGIN
  1028.     myErr := noErr;                        { assume no problems }
  1029.     GetDocumentFromFileGuts(theFile, theDoc, myErr);
  1030.     GetDocumentFromFile := (myErr = noErr);    { return TRUE if no problems }
  1031. END; { GetDocumentFromFile }
  1032.  
  1033. {$S Main}
  1034. (******************************************************************************
  1035. *
  1036. * private: InitializeDefaultDoc
  1037. *
  1038. * This routine takes a document structure and makes it equal to the "default"
  1039. * document you get when you pick "New" from the file menu.  It returns
  1040. * FALSE if it can't initialize the document, though it always can right now.
  1041. *
  1042. ******************************************************************************)
  1043.  
  1044. FUNCTION InitializeDefaultDoc(theDoc: DocumentPtr): BOOLEAN;
  1045.  
  1046. VAR
  1047.     counter: INTEGER;                { loop variable counter }
  1048.  
  1049. BEGIN
  1050.     theDoc^.activeCircle := 1;        { Default active circle }
  1051.     theDoc^.numCircles := 0;        { start with none in document } 
  1052.     theDoc^.ourFileRefNum := 0;     { to indicate never saved to disk }
  1053.     theDoc^.ourFile.vRefNum := 0;
  1054.     theDoc^.ourFile.parID := 0;
  1055.     theDoc^.circleInset := gPrefsRecord.circleInsetSize;
  1056.  
  1057.     FOR counter := 1 TO kDefaultNum DO
  1058.         AddDefaultCircle(theDoc, NIL);
  1059.  
  1060.     CreateWindowTitle(theDoc^.ourFile.name);
  1061.  
  1062.     PrOpen;
  1063.     PrintDefault(theDoc^.printRecord);
  1064.     PrClose;
  1065.  
  1066.     SetDocumentDirtyFlag(theDoc, kDocumentNew); { even though
  1067.                                                   AddDefaultCircle dirties
  1068.                                                   this, we're clean }
  1069.  
  1070.     InitializeDefaultDoc := TRUE; { no way for this to fail presently }
  1071.  
  1072. END; { InitializeDefaultDoc }
  1073.  
  1074. {$S Main}
  1075. (******************************************************************************
  1076. *
  1077. * Public: MakeEmptyDoc
  1078. *
  1079. * This routine allocates all the memory for a new document, allocating the
  1080. * space for the document and for other associated structures like the
  1081. * print record.  If at least kMemoryCushionSize bytes aren't available,
  1082. * we refuse to Create a document, alerting the user that there's a problem.
  1083. * The caller could just as easily Alert the user, but it's more convenient
  1084. * for us to do it here.
  1085. *
  1086. ******************************************************************************)
  1087.  
  1088. FUNCTION MakeEmptyDoc: DocumentPtr;
  1089.  
  1090. VAR
  1091.     theDoc: DocumentPtr;            { the pointer we Allocate with NewPtr }
  1092.     tempHandle: Handle;                { Handle to check the memory cushion }
  1093.  
  1094. BEGIN
  1095.     theDoc := NIL;
  1096.     tempHandle := NewHandle(kMemoryCushionSize);
  1097.     IF tempHandle <> NIL THEN
  1098.         BEGIN
  1099.             DisposeHandle(tempHandle);
  1100.             
  1101.             { NewPtrClear pre-zeroes the Handle, so we don't have to worry
  1102.               about it being filled with garbage.  The system does it faster
  1103.               than we can. }
  1104.             
  1105.             theDoc := DocumentPtr(NewPtrClear(sizeof(document)));
  1106.             
  1107.             IF theDoc <> NIL THEN
  1108.                 theDoc^.printRecord := THPrint(NewHandle(sizeof(TPrint)));
  1109.                 
  1110.         END;
  1111.  
  1112.     { We use the short-circuit OR ("|") here so that we don't dereference
  1113.       theDoc if it's NIL.  For more information on short-circuit booleans,
  1114.       see the description of ClassifyKey in its implementation in the
  1115.       SampleUtilities unit. }
  1116.     
  1117.     IF ((theDoc = NIL) | (theDoc^.printRecord = NIL)) THEN
  1118.         AlertUser(rNoMemForWindow);
  1119.  
  1120.     MakeEmptyDoc := theDoc;
  1121.     
  1122. END; { MakeEmptyDoc }
  1123.  
  1124. {$S Main}
  1125. (******************************************************************************
  1126. *
  1127. * Public: MakeWindowFromDoc
  1128. *
  1129. * This routine creates a new window and associates it with the document
  1130. * passed to it by storing the document pointer in the window's refCon
  1131. * field.  The window is created invisibly (as specified by our 'WIND'
  1132. * resource) so we don't redraw as we resize and change titles.
  1133. * We Create a color window if we have color QuickDraw, and a regular
  1134. * window if we don't.  The window's title is set to the document's
  1135. * file name, and it's resized and shown.  We return the window pointer,
  1136. * including returning NIL if we couldn't Allocate memory for it (but
  1137. * we also Alert the user about this).
  1138. *
  1139. * We use NewPtr and Allocate our own window record storage instead of letting
  1140. * the system do it.  This lets us Handle our own memory management better
  1141. * We try pretty hard not to leave handles locked for very long, and not
  1142. * to Allocate new unrelocatable blocks if other things are locked.  This
  1143. * does a fairly good job of keeping our heap organized.
  1144. *
  1145. * This is a simple strategy, but this is a simple application.
  1146. *
  1147. ******************************************************************************)
  1148.  
  1149. FUNCTION MakeWindowFromDoc(theDoc: DocumentPtr): WindowPtr;
  1150.  
  1151. VAR
  1152.     window: WindowPtr;                { the window we're creating }
  1153.  
  1154. BEGIN
  1155.  
  1156.     window := WindowPtr(NewPtr(sizeof(WindowRecord)));
  1157.     IF window = NIL THEN
  1158.         AlertUser(rNoMemForWindow)
  1159.  
  1160.     ELSE
  1161.         BEGIN
  1162.             IF gHasColorQD THEN
  1163.                 window := GetNewCWindow(rWindow, Ptr(window), WindowPtr( - 1))
  1164.             ELSE
  1165.                 window := GetNewWindow(rWindow, Ptr(window), WindowPtr( - 1));
  1166.  
  1167.             SetPort(window);
  1168.             SetWRefCon(window, LONGINT(theDoc));
  1169.  
  1170.             SetWTitle(window, theDoc^.ourFile.name);
  1171.             AdjustWindowSize(window, theDoc);
  1172.             ShowWindow(window);
  1173.         END;
  1174.  
  1175.     MakeWindowFromDoc := window;
  1176.  
  1177. END; { MakeWindowFromDoc }
  1178.  
  1179. {$S Main}
  1180. (******************************************************************************
  1181. *
  1182. * Public: DoNew
  1183. *
  1184. * DoNew is called by Sample.p every time the user picks the "New" command
  1185. * from the "file" menu, or when we receive an 'oapp' event.  It creates a
  1186. * default, untitled document window and returns the pointer to it, or
  1187. * NIL if this failed.
  1188. *
  1189. ******************************************************************************)
  1190.  
  1191. FUNCTION DoNew: WindowPtr;
  1192.  
  1193. VAR
  1194.     aDocument: DocumentPtr;            { the document we Create }
  1195.     ignore: BOOLEAN;                { ignored function result }
  1196.  
  1197. BEGIN
  1198.     aDocument := MakeEmptyDoc;
  1199.     IF aDocument <> NIL THEN        { if the document was created }
  1200.         BEGIN                        { initialize it and make a window }
  1201.             ignore := InitializeDefaultDoc(aDocument);
  1202.             DoNew := MakeWindowFromDoc(aDocument);
  1203.         END
  1204.     ELSE
  1205.         DoNew := NIL;
  1206. END; { DoNew }
  1207.  
  1208. {$S Main}
  1209. (******************************************************************************
  1210. *
  1211. * Public: DoOpenDocument
  1212. *
  1213. * This routine takes a pointer to a FileLikeSpec for a document on disk and
  1214. * returns a pointer to a new window containing that document.  It calls
  1215. * GetDocumentFromFile to fill in an empty document that it creates.  That
  1216. * routine calls Standard file if the FileLikeSpecPtr is NIL, asking the
  1217. * user to pick a file.  If we can't get a document from disk, the new document
  1218. * structure is disposed.
  1219. *
  1220. * This routine works nicely to return windows whether or not the user
  1221. * needs to open them or not.
  1222. *
  1223. ******************************************************************************)
  1224.  
  1225. FUNCTION DoOpenDocument(theFileSpec: FileLikeSpecPtr): WindowPtr;
  1226.  
  1227. VAR
  1228.     theDocument: DocumentPtr;            { the document we Create }
  1229.     success: BOOLEAN;                    { did DoOpenDocument succeed? }
  1230.  
  1231. BEGIN
  1232.     theDocument := MakeEmptyDoc;
  1233.     success := GetDocumentFromFile(theFileSpec, theDocument);
  1234.     IF success THEN
  1235.         DoOpenDocument := MakeWindowFromDoc(theDocument)
  1236.     ELSE
  1237.         BEGIN
  1238.             DoOpenDocument := NIL;
  1239.             success := DisposeDocument(theDocument); { ignore the result }
  1240.         END;
  1241. END; { DoOpenDocument }
  1242.  
  1243. {$S Main}
  1244. (******************************************************************************
  1245. *
  1246. * Public: DoSave
  1247. *
  1248. * DoSave takes a window pointer and writes the window's document to a disk
  1249. * file, returning TRUE if all went well.  The window's title is also changed
  1250. * to reflect the name of the document on disk, which results in no visible
  1251. * change if it was saved with the same file name.
  1252. *
  1253. ******************************************************************************)
  1254.  
  1255. FUNCTION DoSave(theWindow: WindowPtr): BOOLEAN;
  1256.  
  1257. VAR
  1258.     theDoc: DocumentPtr;
  1259.     returnValue: BOOLEAN;
  1260.  
  1261. BEGIN
  1262.     theDoc := DocumentPtr(GetWRefCon(theWindow));
  1263.     returnValue := PutDocumentToFile(theDoc);
  1264.     IF returnValue THEN
  1265.         SetWTitle(theWindow, theDoc^.ourFile.name);
  1266.     DoSave := returnValue;
  1267. END; { DoSave }
  1268.  
  1269. {$S Main}
  1270. (******************************************************************************
  1271. *
  1272. * Public: DoSaveAs
  1273. *
  1274. * DoSaveAs is largely like DoSave, except it needs to _always_ prompt the
  1275. * user for a new file in which to store the document.  To do this, it
  1276. * doesn't close the existing file until the new one is saved, so it can
  1277. * still cancel if there was an error.
  1278. *
  1279. * The routine returns TRUE if the document was succesfully saved in a new
  1280. * place.
  1281. *
  1282. ******************************************************************************)
  1283.  
  1284. FUNCTION DoSaveAs(theWindow: WindowPtr): BOOLEAN;
  1285.  
  1286. VAR
  1287.     theDoc: DocumentPtr;                    { this window's document structure }
  1288.     myErr: OSErr;                            { error from file Manager calls }
  1289.     returnValue: BOOLEAN;                    { return from PutDocumentToFile }
  1290.     oldRefNum: INTEGER;                        { current refNum, for storage }
  1291.     oldFileLikeSpec: FileLikeSpec;            { current FileLikeSpec, for storage }
  1292.  
  1293. BEGIN
  1294.     theDoc := DocumentPtr(GetWRefCon(theWindow));
  1295.     oldRefNum := theDoc^.ourFileRefNum;
  1296.     oldFileLikeSpec := theDoc^.ourFile;
  1297.  
  1298.     returnValue := PutDocumentToFile(theDoc);
  1299.     IF returnValue THEN
  1300.         BEGIN
  1301.             SetWTitle(theWindow, theDoc^.ourFile.name);
  1302.             IF oldRefNum <> 0 THEN
  1303.                 BEGIN
  1304.                     myErr := FSClose(oldRefNum);
  1305.                     IF myErr <> noErr THEN
  1306.                         HandleFileError(myErr, oldFileLikeSpec.name);
  1307.                 END;
  1308.         END
  1309.     ELSE
  1310.         BEGIN
  1311.             theDoc^.ourFileRefNum := oldRefNum;
  1312.             theDoc^.ourFile := oldFileLikeSpec;
  1313.         END;
  1314.  
  1315.     DoSaveAs := returnValue;
  1316. END; { DoSaveAs }
  1317.  
  1318. {$S Main}
  1319. (******************************************************************************
  1320. *
  1321. * Public: CloseAppWindow
  1322. *
  1323. * CloseAppWindow is a "shell" function, called by Sample.p every time it
  1324. * wants to close a window that belongs to the application.  (Sample.p already
  1325. * knows how to close desk accessory windows under System 6, for example.)
  1326. *
  1327. * If the document is dirty, and if we can interact with the user, we ask
  1328. * the user if changes should be saved.  If they say "Save," we call DoSave
  1329. * to save the file, returning TRUE if this completes without error.
  1330. * If they say "Don't Save", we close the window, dispose of the window record
  1331. * and return TRUE.  If they cancel, or if there's an error in saving, we return 
  1332. * FALSE to tell the caller "Hey, this window didn't get closed."
  1333. *
  1334. * "action" is a constant, either kClosing or kQuitting, so we can tell the
  1335. * user what's going on in the prompt.
  1336. *
  1337. ******************************************************************************)
  1338.  
  1339. FUNCTION CloseAppWindow(theWindow: WindowPtr; action: INTEGER): BOOLEAN;
  1340.  
  1341. VAR
  1342.     theDoc: DocumentPtr;            { this window's document structure }
  1343.     result: INTEGER;                { result of asking the user to save }
  1344.  
  1345. BEGIN
  1346.     theDoc := DocumentPtr(GetWRefCon(theWindow));
  1347.  
  1348.     CloseAppWindow := FALSE;         { assume we're not going to close }
  1349.     result := kDontSave;             { assume we're going to close without 
  1350.                                       saving }
  1351.  
  1352.     IF (GetDocumentDirtyFlag(theDoc) = kDocumentDirty) THEN
  1353.         BEGIN
  1354.             result := DoPromptSave(theDoc^.ourFile.name, action);
  1355.             IF result = kSave THEN
  1356.                 IF NOT DoSave(theWindow) THEN
  1357.                     result := kCancel; { if saving got an error, cancel the
  1358.                                          close operation }
  1359.         END;
  1360.  
  1361.     IF result <> kCancel THEN
  1362.         BEGIN
  1363.             CloseWindow(theWindow);
  1364.             
  1365.             { We dispose of the window record's memory here because
  1366.               we allocated it, not the window Manager. }
  1367.             
  1368.             DisposePtr(Ptr(theWindow));
  1369.             CloseAppWindow := DisposeDocument(theDoc);
  1370.         END;
  1371. END; { CloseAppWindow }
  1372.  
  1373. {$S Main}
  1374. (******************************************************************************
  1375. *
  1376. * private: OpenPrefsFile
  1377. *
  1378. * This routine finds our preferences file on the boot volume and opens it,
  1379. * returning the refNum of the resource file.  If one isn't found, it creates it.
  1380. *
  1381. * The preferences file is supposed to be in the "Preferences" folder as
  1382. * defined by FindFolder, and I would have sworn I once read a Technical Note
  1383. * saying to Create and use such a folder under System 6 as well.  However,
  1384. * I can find no record of such a note, and the FindFolder glue for System 6
  1385. * maps the kPreferencesFolderType to the system folder, giving strong argument
  1386. * that preferences files should be loose in the system folder under System 6.
  1387. * so that's what we do.
  1388. *
  1389. * THINK Pascal doesn't support the glue for FindFolder, meaning we can't
  1390. * call it unless the trap is implemented.  MPW Pascal 3.0 and later do support
  1391. * the glue.  so, we only call FindFolder if the trap is implemented
  1392. * (gHasFindFolder = TRUE) or if we're not using THINK Pascal.  Otherwise,
  1393. * we use SysEnvirons and GetWDInfo to get the vRefNum and dirID of the
  1394. * system folder.
  1395. *
  1396. * Once we have the folder, we try to open the file.  If it's not found,
  1397. * we attempt to Create it and set the creator and file type appropriately.
  1398. * Then we try again to open it, returning -1 if we can't make a preferences
  1399. * file available.
  1400. *
  1401. ******************************************************************************)
  1402.  
  1403. FUNCTION OpenPrefsFile: INTEGER;
  1404.  
  1405. VAR
  1406.     myErr: OSErr;                        { error from system calls }
  1407.     foundVRefNum,                        { vRefNum for system folder }
  1408.     prefsRefNum: INTEGER;                { refNum for our preferences file }
  1409.     foundDirID,                            { dirID of the system folder }
  1410.     ignoreThis: LONGINT;                { unused procID for GetWDInfo }
  1411.     prefsFileName: Str255;                { name for our preferences file }
  1412.     prefsFileNameHandle: StringHandle;    { file name Handle returned by GetString }
  1413.     ourFInfo: FInfo;                    { for setting our file and creator type }
  1414.     ThinkPascal: BOOLEAN;                { TRUE if this is THINK Pascal }
  1415.     mySysEnvRec: SysEnvRec;                { for SysEnvirons call if no FindFolder }
  1416.  
  1417. BEGIN
  1418.     prefsRefNum := - 1;                 { couldn't open the file }
  1419.     prefsFileNameHandle := GetString(rPrefsFileName);
  1420.     BlockMove(Ptr(prefsFileNameHandle^), @prefsFileName,
  1421.               length(prefsFileNameHandle^^) + 1); 
  1422.                                           { make static copy of file name }
  1423.     ReleaseResource(Handle(prefsFileNameHandle)); { free up the String's space }
  1424.  
  1425.     {$IFC UNDEFINED Think_Pascal}
  1426.     ThinkPascal := FALSE;
  1427.     {$ELSEC}
  1428.     ThinkPascal := TRUE;
  1429.     {$ENDC}
  1430.  
  1431.     { THINK Pascal requires the FindFolder trap, so don't use it if we can't }
  1432.     
  1433.     IF gHasFindFolder OR (NOT ThinkPascal) THEN
  1434.         myErr := FindFolder(kOnSystemDisk, kPreferencesFolderType,
  1435.                             kCreateFolder, foundVRefNum, foundDirID)
  1436.     ELSE
  1437.         BEGIN
  1438.             
  1439.             { No FindFolder, so get the system folder info from SysEnvirons }
  1440.             
  1441.             myErr := SysEnvirons(curSysEnvVers, mySysEnvRec);
  1442.             IF myErr = noErr THEN
  1443.                 myErr := GetWDInfo(mySysEnvRec.sysVRefNum, foundVRefNum,
  1444.                                    foundDirID, ignoreThis);
  1445.         END;
  1446.  
  1447.     IF myErr = noErr THEN
  1448.         BEGIN
  1449.             prefsRefNum := HOpenResFile(foundVRefNum, foundDirID, prefsFileName,
  1450.                                         fsRdWrPerm);
  1451.             IF ((prefsRefNum = - 1) AND (ResError = fnfErr)) THEN
  1452.                 BEGIN
  1453.                     
  1454.                     { Create the missing prefs file if possible, and set the
  1455.                       file and creator types correctly }
  1456.                     
  1457.                     HCreateResFile(foundVRefNum, foundDirID, prefsFileName);
  1458.                     myErr := ResError;
  1459.                     myErr := HGetFInfo(foundVRefNum, foundDirID, prefsFileName,
  1460.                                        ourFInfo);
  1461.                     ourFInfo.fdType := kSamplePrefsType;
  1462.                     ourFInfo.fdCreator := kOurCreatorType;
  1463.                     myErr := HSetFInfo(foundVRefNum, foundDirID, prefsFileName,
  1464.                                        ourFInfo);
  1465.                     prefsRefNum := HOpenResFile(foundVRefNum, foundDirID,
  1466.                                                 prefsFileName, fsCurPerm);
  1467.                 END;
  1468.             OpenPrefsFile := prefsRefNum;
  1469.         END;                             { if HOpenResFile didn't work }
  1470. END; { OpenPrefsFile }
  1471.  
  1472. {$S Main}
  1473. (******************************************************************************
  1474. *
  1475. * Public: PutPrefsToFile
  1476. *
  1477. * This routine writes a preferences record as a resource of type
  1478. * kSamplePrefsRsrc and ID rSamplePrefsID to the resource file referenced
  1479. * by theRefNum.  The resource format is the same as the record format.
  1480. * If the reference number is anything but -1, it's the refNum of an open
  1481. * resource file to write to.  If it's -1, we call OpenPrefsFile to open
  1482. * a preferences file.
  1483. *
  1484. ******************************************************************************)
  1485.  
  1486. PROCEDURE PutPrefsToFile(thePrefs: preferences; theRefNum: INTEGER);
  1487.  
  1488. VAR
  1489.     newRefNum,                            { refNum for new file if needed }
  1490.     oldResFile: INTEGER;                { current resource file on entry }
  1491.     ourPrefsHandle: Handle;                { Handle to the prefs record }
  1492.     testPrefsHandle: Handle;            { Handle to test if there's already
  1493.                                           a prefs resource in the file }
  1494.  
  1495. BEGIN
  1496.     oldResFile := CurResFile;            { save the original resource file }
  1497.  
  1498.     IF theRefNum = -1 THEN                { did we reference a real file? }
  1499.         newRefNum := OpenPrefsFile        { No, so go open one for us }
  1500.     ELSE
  1501.         newRefNum := theRefNum;            { yes, so use it }
  1502.  
  1503.     UseResFile(newRefNum);                { switch to our resource file }
  1504.     ourPrefsHandle := NewHandle(sizeof(preferences));
  1505.                                         { make a Handle for the prefs }
  1506.     BlockMove(@thePrefs, ourPrefsHandle^, sizeof(preferences));
  1507.     
  1508.     { test to see if there's already a preferences resource.  If there is,
  1509.       remove it so we can add this one. }
  1510.     
  1511.     testPrefsHandle := Get1Resource(kSamplePrefsRsrc, rSamplePrefsID);
  1512.     IF testPrefsHandle <> NIL THEN
  1513.         BEGIN
  1514.             RmveResource(testPrefsHandle);
  1515.             DisposeHandle(testPrefsHandle);
  1516.         END;
  1517.     AddResource(ourPrefsHandle, kSamplePrefsRsrc, rSamplePrefsID, '');
  1518.     IF theRefNum = -1 THEN                { if we opened the file ... }
  1519.         CloseResFile(newRefNum);        { ...close it when done }
  1520.     UseResFile(oldResFile);                { restore the old resource file }
  1521.  
  1522. END; { PutPrefsToFile }
  1523.  
  1524. {$S Main}
  1525. (******************************************************************************
  1526. *
  1527. * private: GetPrefsFromFile
  1528. *
  1529. * This routine fills in a preferences record with the contents of the
  1530. * prefs resource in our prefs file.  If there is no existing preferences
  1531. * file, we fill in the record with hard-coded default values.
  1532. *
  1533. * If there was a preferences file, and if we created default preferences,
  1534. * we write them to the file.
  1535. *
  1536. ******************************************************************************)
  1537.  
  1538. PROCEDURE GetPrefsFromFile(VAR thePrefs: preferences);
  1539.  
  1540. VAR
  1541.     prefsRefNum: INTEGER;            { refNum of the preferences files }
  1542.     doDefaultPrefs: BOOLEAN;        { TRUE if we use the default prefs }
  1543.     prefsHandle: Handle;            { Handle containing a preferences record }
  1544.  
  1545. BEGIN
  1546.     doDefaultPrefs := FALSE;        { assume no defaults }
  1547.     prefsRefNum := OpenPrefsFile;    { open the preferences file }
  1548.     IF prefsRefNum = -1 THEN        { Was there one to open? }
  1549.         doDefaultPrefs := TRUE        { if not, return defaults }
  1550.     ELSE
  1551.         BEGIN                        { there was, so get the resource }
  1552.             prefsHandle := GetResource(kSamplePrefsRsrc, rSamplePrefsID);
  1553.             IF GetHandleSize(prefsHandle) <> sizeof(preferences) THEN
  1554.                 doDefaultPrefs := TRUE
  1555.             ELSE
  1556.                 BlockMove(prefsHandle^, @thePrefs, sizeof(preferences));
  1557.         END;
  1558.  
  1559.     IF doDefaultPrefs THEN
  1560.         BEGIN
  1561.             WITH gPrefsRecord DO
  1562.                 BEGIN
  1563.                     circleRectSize := 100;
  1564.                     circleInsetSize := 5;
  1565.                     maxNumCircles := 6;
  1566.                 END;
  1567.         END;
  1568.  
  1569.     IF (prefsRefNum <> -1) AND doDefaultPrefs THEN
  1570.         BEGIN
  1571.             PutPrefsToFile(gPrefsRecord, prefsRefNum);
  1572.             IF NOT doDefaultPrefs THEN
  1573.                 CloseResFile(prefsRefNum);
  1574.         END;
  1575.  
  1576. END; { GetPrefsFromFile }
  1577.  
  1578. {$S Main}
  1579. (******************************************************************************
  1580. *
  1581. * Public: InstallAppAEHandlers
  1582. *
  1583. * This shell function is called by Sample.p after it installs its Apple
  1584. * Event handlers.  We'd install any application-specific event handlers here,
  1585. * but we don't have any so it's an empty procedure.
  1586. *
  1587. ******************************************************************************)
  1588.  
  1589. PROCEDURE InstallAppAEHandlers;
  1590.  
  1591. BEGIN
  1592. END; { InstallAppAEHandlers }
  1593.  
  1594. {$S Main}
  1595. (******************************************************************************
  1596. *
  1597. * Public: InitializeApplication
  1598. *
  1599. * This routine is called by Sample.p after it's initialized all the
  1600. * stuff not specific to this unit.  Here's where we read the preferences,
  1601. * initialize our window counts and take care of startup tasks.
  1602. *
  1603. * If there's no Apple Event Manager, we need to call the Segment Loader
  1604. * to read old-style AppFiles records and open any windows they specify.
  1605. * If CountAppFiles returns zero, we open a new untitled window.  Unlike the
  1606. * Apple Event 'pdoc' spec, in the old world we open windows for documents
  1607. * before we print them, and we return FALSE from this routine so that
  1608. * we don't proceed with normal application work.  This makes us quit after
  1609. * printing files from GetAppFiles, which is what we're supposed to do.
  1610. *
  1611. * The routine returns FALSE if we couldn't initialize something.  Sample.p
  1612. * will quit if this routine returns FALSE.
  1613. *
  1614. ******************************************************************************)
  1615.  
  1616. FUNCTION InitializeApplication: BOOLEAN;
  1617.  
  1618. VAR
  1619.     window: WindowPtr;                    { window for files we open }
  1620.     fileAction: INTEGER;                { what to do with each file }
  1621.     fileCount: INTEGER;                    { how many AppFiles are there? }
  1622.     count: INTEGER;                        { loop variable counter }
  1623.     myAppFile: AppFile;                    { record for GetAppFiles }
  1624.     myFileSpec: FileLikeSpec;            { container to specify a file to open }
  1625.     ourVRefNum: INTEGER;                { vRefNum for file to open }
  1626.     ourDirID: LONGINT;                    { dirID for file to open }
  1627.     myErr: OSErr;                        { error from system calls }
  1628.     procID: LONGINT;                    { ignored procID for GetWDInfo }
  1629.     ignore: BOOLEAN;                    { ignored result from DoPrint }
  1630.  
  1631. BEGIN
  1632.     GetPrefsFromFile(gPrefsRecord);
  1633.     gUntitledWindowCount := 0;
  1634.     InitializeApplication := TRUE;
  1635.  
  1636.     { Make a new untitled window only if we can't get an 'odoc' event, or if
  1637.       there are no files on disk to open or print }
  1638.  
  1639.     IF NOT gHasAppleEvents THEN
  1640.         BEGIN
  1641.             CountAppFiles(fileAction, fileCount);
  1642.             IF fileCount > 0 THEN
  1643.                 FOR count := 1 TO fileCount DO
  1644.                     BEGIN
  1645.                         GetAppFiles(count, myAppFile);
  1646.                         procID := 0;
  1647.                         myErr := GetWDInfo(myAppFile.vRefNum, ourVRefNum,
  1648.                                            ourDirID, procID);
  1649.                         IF (myErr = noErr) AND (myAppFile.fType =
  1650.                            kOurDocumentType) THEN
  1651.                             BEGIN
  1652.                                 myFileSpec.vRefNum := ourVRefNum;
  1653.                                 myFileSpec.parID := ourDirID;
  1654.                                 myFileSpec.name := myAppFile.fName;
  1655.                                 window := DoOpenDocument(@myFileSpec);
  1656.                                 IF fileAction = appPrint THEN
  1657.                                     BEGIN
  1658.                                         InitializeApplication := FALSE;
  1659.                                         ignore := DoPrint(window);
  1660.                                     END;     { IF fileAction = appPrint }
  1661.                             END;            { IF this is out file to open }
  1662.                     END                        { FOR count := 1 to fileCount DO }
  1663.             ELSE
  1664.                 window := DoNew;
  1665.         END;
  1666.     
  1667.     { We keep track of which rectangle the user last clicked in, so we can
  1668.       do double-click actions if he clicks in it twice within DblTime.  Here
  1669.       we initialize that to -1 so it can't possibly succeed on his first click. }
  1670.     
  1671.     gLastRectClicked := -1; { make sure the double-click test fails the first
  1672.                               time }
  1673. END; { InitializeApplication }
  1674.  
  1675. {$S Main}
  1676. (******************************************************************************
  1677. *
  1678. * Public: RemoveAppAEHandlers
  1679. *
  1680. * This shell function is called by Sample.p after it removes its Apple
  1681. * Event handlers.  We'd remove any application-specific event handlers here,
  1682. * but we don't have any so it's an empty procedure.
  1683. *
  1684. ******************************************************************************)
  1685.  
  1686. PROCEDURE RemoveAppAEHandlers;
  1687.  
  1688. BEGIN
  1689. END; { RemoveAppAEHandlers }
  1690.  
  1691. {$S Main}
  1692. (******************************************************************************
  1693. *
  1694. * Public: TerminateApplication
  1695. *
  1696. * Sample.p calls TerminateApplication from within Terminate, to give this
  1697. * unit a chance to shut down anything specific it has going without having
  1698. * to put it in the main code.  We don't really have anything to do, so 
  1699. * we simply return TRUE, saying "Yeah, we're ready to quit."
  1700. *
  1701. * If we had a Handle for the preferences record, for example, this is where
  1702. * we'd dispose it.
  1703. *
  1704. ******************************************************************************)
  1705.  
  1706. FUNCTION TerminateApplication: BOOLEAN;
  1707.  
  1708. BEGIN
  1709.     TerminateApplication := TRUE;
  1710. END; { TerminateApplication }
  1711.  
  1712. {$S Main}
  1713. (******************************************************************************
  1714. *
  1715. * Public: ChangeCircleColor
  1716. *
  1717. * This routine presents the Macintosh color Picker dialog on the deepest
  1718. * screen (by setting the Point to (-1, -1)) and, if the user picks a new
  1719. * color, stores that color in the circle record.  We start with the circle's
  1720. * existing color.
  1721. ******************************************************************************)
  1722.  
  1723. PROCEDURE ChangeCircleColor(theCircle: CircleRecPtr);
  1724.  
  1725. VAR
  1726.     tempRGB: RGBColor;                { the color we get from the color picker }
  1727.     where: Point;                    { where to place the color picker dialog }
  1728.     promptStr: Str255;                { the prompt String for the dialog }
  1729.  
  1730. BEGIN
  1731.     where.h := -1;
  1732.     where.v := -1;                     { Let the color Picker center this nicely }
  1733.     GetIndString(promptStr, rMiscStrings, kPickColor);        { get the prompt }
  1734.  
  1735.     IF GetColor(where, promptStr, theCircle^.circleColor, tempRGB) THEN
  1736.         theCircle^.circleColor := tempRGB;
  1737. END; { ChangeCircleColor }
  1738.  
  1739. {$S Main}
  1740. (******************************************************************************
  1741. *
  1742. * Public: ChangeCircleFont
  1743. *
  1744. * This was actually one of the first accessor functions I wound up adding,
  1745. * because it became difficult to manipulate a circle record from another
  1746. * twisted, long-since-improved portion of the code.  It reminded me how
  1747. * accessor functions are generally good things, and so they now exist for
  1748. * most CircleRec members.
  1749. *
  1750. * This one stores a new font name in the given circle record.
  1751. ******************************************************************************)
  1752.  
  1753. PROCEDURE ChangeCircleFont(theCircle: CircleRecPtr; theNewFontName: Str255);
  1754.  
  1755. BEGIN
  1756.     theCircle^.circleFont := theNewFontName;
  1757. END; { ChangeCircleFont }
  1758.  
  1759. {$S Main}
  1760. (******************************************************************************
  1761. *
  1762. * Public: ChangeCircleTxSize
  1763. *
  1764. * This routine sets the given circle's circleTxSize field to theNewSize.
  1765. ******************************************************************************)
  1766.  
  1767. PROCEDURE ChangeCircleTxSize(theCircle: CircleRecPtr; theNewSize: INTEGER);
  1768.  
  1769. BEGIN
  1770.     theCircle^.circleTxSize := theNewSize;
  1771. END; { ChangeCircleTxSize }
  1772.  
  1773. {$S Main}
  1774. (******************************************************************************
  1775. *
  1776. * Public: ChangeCircleText
  1777. *
  1778. * This routine sets the given circle's circleText field to theNewText.
  1779. ******************************************************************************)
  1780.  
  1781. PROCEDURE ChangeCircleText(theCircle: CircleRecPtr; theNewText: Str255);
  1782.  
  1783. BEGIN
  1784.     theCircle^.circleText := theNewText;
  1785. END; { ChangeCircleText }
  1786.  
  1787. {$S Main}
  1788. (******************************************************************************
  1789. *
  1790. * Public: ChangeCircleStyle
  1791. *
  1792. * This routine sets the given circle's circleFace field to theNewStyle.
  1793. ******************************************************************************)
  1794.  
  1795. PROCEDURE ChangeCircleStyle(theCircle: CircleRecPtr; theNewStyle: Style);
  1796.  
  1797. BEGIN
  1798.     theCircle^.circleFace := theNewStyle;
  1799. END; { ChangeCircleStyle }
  1800.  
  1801. {$S Main}
  1802. (******************************************************************************
  1803. *
  1804. * private: DrawLightParts
  1805. *
  1806. * You'd think drawing simple filled colored circles with text in them would
  1807. * be a pretty simple thing to do.  Ha.  Ye of little paranoia.
  1808. *
  1809. * If we have color QuickDraw, an "on" light is drawn with a colored
  1810. * circle, a black frame and text.  If the background color is darker than 50%
  1811. * gray, the text is white, otherwise the text is black.  "Off" lights
  1812. * are black framed circles with white interiors and no text.
  1813. *
  1814. * If we don't have color QuickDraw, or if the monitor is one or two-bit deep, 
  1815. * an "on" light is white text on a black background.  "Off" lights are black
  1816. * framed circles with white interiors and no text.
  1817. *
  1818. * in either case, it changes if the window isn't active.  "On" lights in
  1819. * inactive windows are a black framed circle with a white background and black
  1820. * text.  "Off" lights aren't drawn at all.
  1821. *
  1822. * The text is centered in the circle -- the horizontal center of the text
  1823. * is the center of the circle.  The vertical pen location before drawing
  1824. * the text needs to be set so that the Line going through the center of
  1825. * the text also goes through the circle's center.  Normally, the vertical
  1826. * position before drawing text is the character's baseline, so we have to
  1827. * Move to the vertical center of the circle, and then Move then pen down
  1828. * by half of the fontHeight.  This is tricky, so please look at the font
  1829. * pictures in Inside Macintosh and play with it if you don't understand
  1830. * why this works.
  1831. *
  1832. * We only draw text if the Point size is greater than zero (if you pass zero
  1833. * to TextSize, you get the system default, which is usually 12, and that's
  1834. * not very intuitive for interactive situations like the Modify Circle
  1835. * dialog), if there are characters to draw, and if the light is on, to
  1836. * avoid a mess of computations.
  1837. *
  1838. * Once we have everything ready, we still can't tell if the String we're
  1839. * about to draw fits into the circle or not.  Imagine the String being the
  1840. * boundary rectangle for the text, and it being exactly as wide as the circle
  1841. * is at its widest Point.  StringWidth would say "this isn't too wide,"
  1842. * but the circle gets narrower above and below the middle, so the top and
  1843. * bottom of the rectangle would be outside the circle.  The only way
  1844. * to know exactly which characters would fall outside the circle would be
  1845. * to know how QuickDraw draws a circle (so we can compare), or to see
  1846. * if all the text falls within a circular Region.  If we're going to
  1847. * do that, we might as well just clip to a circular Region, so we do.
  1848. *
  1849. * The drawback is that non-rectangular clipping regions don't work on
  1850. * PostScript printers.  If the text would exceed the circle's boundaries,
  1851. * PostScript printers only clip to the rectangular bounding box of the
  1852. * Region, not the circular Region itself.  There's no way to layer this
  1853. * so that it does what we want on the screen and on PostScript printers
  1854. * before QuickDraw GX, where clipping to an arbitrary shape works on
  1855. * all printer types.
  1856. *
  1857. * This routine is usually called from another routine -- either DeviceLoop
  1858. * or DeviceLoopSim (SampleUtilities.p), which call it separately for each
  1859. * monitor the given light intersects.  The depth parameter tells us how
  1860. * deep the monitor is.  If it's zero, we called it directly and assume
  1861. * there's no color QuickDraw.  We don't use the flags passed to us by
  1862. * those routines, nor do we use the targetDevice.  The final parameter
  1863. * is a LightConditionsPtr, which tells us the CircleRec and the state
  1864. * of the light (is it on, is it active, are we printing).
  1865. *
  1866. * Since this routine is called from elsewhere, it saves and restores
  1867. * values it changes in the current GrafPort.  Macintosh Technical Note
  1868. * "Old-Style colors" recommends directly saving and restoring values
  1869. * in the GrafPort since we can't get RGB colors from it, so we do that.
  1870. * If 32-Bit QuickDraw is around, we call PortChanged after restoring the
  1871. * old values so any accelerator card can figure out we mucked directly
  1872. * with the GrafPort structure.
  1873. *
  1874. ******************************************************************************)
  1875.  
  1876. PROCEDURE DrawLightParts(depth: INTEGER; flags: INTEGER; targetDevice: GDHandle;
  1877.                          myLight: LightConditionsPtr);
  1878.  
  1879. VAR
  1880.     oldForeColor,                    { port's ForeColor for restoring later }
  1881.     oldBackColor,                    { port's BackColor for restoring later }
  1882.     colorTemp: LONGINT;                { normalized "brightness" value for color }
  1883.     rectWidth,                        { width of this circle's rectangle }
  1884.     rectHeight,                        { height of this circle's rectangle }
  1885.     fontHeight,                        { height of the font not including leading }
  1886.     stringBtm,                        { Y position for pen before drawing text }
  1887.     stringLeft,                        { X position for pen before drawing text }
  1888.     oldTxMode,                        { port's txMode for restoring later }
  1889.     oldTxFont,                        { port's txFont for restoring later }
  1890.     oldTxSize,                        { port's txSize for restoring later }
  1891.     theFont,                        { family number of this circle's font }
  1892.     frameColor,                        { old-style color for frame of circle }
  1893.     ourTextMode: INTEGER;            { mode we draw the circle's text with }
  1894.     fontStuff: FontInfo;            { record with info about circle's font }
  1895.     ourPort: GrafPtr;                { the current GrafPort }
  1896.     theOldClip,                        { the port's old clipRgn, for restoring }
  1897.     ourCircleRgn,                    { circular Region for this circle }
  1898.     newClipRgn: RgnHandle;            { new clipRgn for clipping }
  1899.     useColor: BOOLEAN;                { TRUE if we draw with color }
  1900.  
  1901. BEGIN
  1902.  
  1903.     { First, check to see if we're going to draw anything.  Bail out quickly
  1904.       if everything would be clipped out.  Make a Region that includes this
  1905.       port's clipRgn and visRgn and see if our circle's rectangle intersects
  1906.       it.  If it doesn't, skip the rest of this routine. }
  1907.  
  1908.     ourCircleRgn := NewRgn;
  1909.     GetPort(ourPort);
  1910.     SectRgn(ourPort^.clipRgn, ourPort^.visRgn, ourCircleRgn);
  1911.     IF RectInRgn(myLight^.theCircle^.circleRect, ourCircleRgn) OR
  1912.        (myLight^.arePrinting) THEN
  1913.         WITH myLight^.theCircle^ DO
  1914.             BEGIN
  1915.                 
  1916.                 { If we're passed a depth of zero, we need to check to see
  1917.                   if this is a CGrafPort (rowBytes is negative in a CGrafPort).
  1918.                   We call this routine directly for printing, so if we don't
  1919.                   do this, we won't get color output on color printers.
  1920.                   If depth is not zero, use color if we're four bits deep
  1921.                   or deeper. }
  1922.                 
  1923.                 IF depth = 0 THEN
  1924.                     useColor := (ourPort^.portBits.rowBytes < 0)
  1925.                 ELSE
  1926.                     useColor := (depth > 2);
  1927.  
  1928.                 IF useColor THEN
  1929.                     BEGIN
  1930.                         
  1931.                         { The RGB values for the color are signed numbers,
  1932.                           but to do our simple test, we want to average them
  1933.                           together and see if the average is greater than
  1934.                           32768.  If it is, it's a light color and the text
  1935.                           will be black -- otherwise it's a dark color and
  1936.                           the text will be white.  To "normalize" the signed
  1937.                           values, we add $10000 to them if they're negative.
  1938.                           Then we average them together. }
  1939.                         
  1940.                         colorTemp := circleColor.red + ($10000 *
  1941.                                      ord(circleColor.red < 0));
  1942.                         colorTemp := colorTemp + circleColor.blue + ($10000 *
  1943.                                      ord(circleColor.blue < 0));
  1944.                         colorTemp := colorTemp + circleColor.green +
  1945.                                      ($10000 * ord(circleColor.green < 0));
  1946.                         colorTemp := colorTemp DIV 3;
  1947.                     END;
  1948.  
  1949.                 { Set the frame for the circle -- black if the light is on or
  1950.                   if it's off in an active window, white otherwise so it
  1951.                   doesn't show up. }
  1952.                 
  1953.                 IF myLight^.itsOn OR myLight^.itsActive THEN
  1954.                     frameColor := blackColor
  1955.                 ELSE
  1956.                     frameColor := whiteColor;
  1957.  
  1958.                 { Save the curent values for these fields so we can restore
  1959.                   them later }
  1960.                 
  1961.                 oldForeColor := thePort^.fgColor; 
  1962.                 oldBackColor := thePort^.bkColor;
  1963.                 oldTxMode := thePort^.txMode;
  1964.                 oldTxFont := thePort^.txFont;
  1965.                 oldTxSize := thePort^.txSize;
  1966.  
  1967.                 { Now set the port's ForeColor in preparation for drawing 
  1968.                   the circle.  If the light is "on" and in an active window,
  1969.                   then use RGBForeColor if we can use color and if we have
  1970.                   color QuickDraw.  Otherwise, the circle is black. 
  1971.                   
  1972.                   If the light isn't "on" or in an active window, we set
  1973.                   the pen mode to patBic instead to get QuickDraw to
  1974.                   erase the circle when we paint it.  This works better than
  1975.                   ForeColor(whiteColor) for printing, especially with LaserWriter
  1976.                   driver 7.x, where all colors, even white, lead to black. }
  1977.  
  1978.                 IF myLight^.itsActive AND myLight^.itsOn THEN
  1979.                     IF (useColor) AND gHasColorQD THEN
  1980.                         RGBForeColor(circleColor)
  1981.                     ELSE
  1982.                         ForeColor(blackColor)
  1983.                 ELSE
  1984.                     PenMode(patBic);
  1985.  
  1986.                 { Draw the circle and its frame, after all this setup }
  1987.                 
  1988.                 PaintOval(circleRect);
  1989.                 PenMode(patCopy);
  1990.                 ForeColor(frameColor);
  1991.                 FrameOval(circleRect);
  1992.  
  1993.  
  1994.                 { Now prepare to draw the text in the text color on the
  1995.                   circle's colored background, centered.  Avoid drawing if
  1996.                   the size or length is zero, and if the light isn't "on."
  1997.                   
  1998.                   We change the pen mode to do black or white drawing.  If
  1999.                   we use color, if the circle color is "light" (colorTemp
  2000.                   > 32768), or if the light is in an inactive window, the
  2001.                   text is black.  Otherwise, we use text mode srcBic to
  2002.                   clear the pixels instead of setting them, producing
  2003.                   white text.  Again, this works better with printing.
  2004.                   Otherwise, we use srcOr to draw the text in black, as 
  2005.                   Inside Macintosh recommends, in black. }
  2006.  
  2007.                 ForeColor(blackColor);
  2008.                 IF ((myLight^.itsOn) AND (length(circleText) > 0) AND
  2009.                    (circleTxSize > 0)) THEN
  2010.                     BEGIN
  2011.                         IF ((useColor) AND (colorTemp > 32768)) OR
  2012.                            NOT myLight^.itsActive THEN
  2013.                             ourTextMode := srcOr
  2014.                         ELSE
  2015.                             BEGIN
  2016.                                 ForeColor(whiteColor);
  2017.                                 ourTextMode := srcBic
  2018.                             END;
  2019.                         
  2020.                         { Get the width and height of the rectangle, find
  2021.                           the font number for the circle, set the text
  2022.                           drawing parameters appropriately for the circle }
  2023.                         
  2024.                         rectWidth := abs(circleRect.right -
  2025.                                      circleRect.left);
  2026.                         rectHeight := abs(circleRect.bottom -
  2027.                                           circleRect.top);
  2028.  
  2029.                         GetFNum(circleFont, theFont); { Turn the name into a
  2030.                                                        font number }
  2031.                         TextFont(theFont); { and install that font into
  2032.                                             thePort }
  2033.                         TextSize(circleTxSize); { with the given size }
  2034.                         TextFace(circleFace);
  2035.  
  2036.                         { Get the font height.  Find the halfway Point of the
  2037.                           circle by adding half the circle's height to the
  2038.                           circle's top.  Then add half the font's height and
  2039.                           subtract the baseline to find where to draw the
  2040.                           text.  Horizontally center the text by moving to 
  2041.                           the left edge of the circle, plus half the width
  2042.                           of the circle, minus half the width of the String. }
  2043.                         
  2044.                         GetFontInfo(fontStuff);
  2045.                         fontHeight := fontStuff.ascent + fontStuff.descent;
  2046.                         stringBtm := (rectHeight DIV 2) + (fontHeight DIV
  2047.                                      2) + circleRect.top - fontStuff.
  2048.                                      descent;
  2049.                         stringLeft := circleRect.left + ((rectWidth DIV 2) -
  2050.                                       (StringWidth(circleText) DIV 2));
  2051.                         MoveTo(stringLeft, stringBtm);
  2052.  
  2053.                         TextMode(ourTextMode);
  2054.  
  2055.                         { Create a circular clipping Region so the text won't
  2056.                           exceed the circle's boundaries on-screen or to most
  2057.                           printers.  Save and restore the old clipping Region. }
  2058.  
  2059.                         theOldClip := NewRgn;
  2060.                         GetClip(theOldClip);
  2061.  
  2062.                         newClipRgn := NewRgn;
  2063.                         OpenRgn;
  2064.                         FrameOval(circleRect);
  2065.                         CloseRgn(newClipRgn);
  2066.                         SetClip(newClipRgn);
  2067.  
  2068.                         DrawString(circleText);        { draw the text!  yay! }
  2069.  
  2070.                         SetClip(theOldClip);
  2071.                         
  2072.                         DisposeRgn(theOldClip);
  2073.                         DisposeRgn(newClipRgn);
  2074.  
  2075.                     END;    { IF we should draw text THEN draw it }
  2076.  
  2077.                 { Now back out of all this.  Use trap calls to restore the
  2078.                   text parameters and tell 32-Bit QuickDraw we did some
  2079.                   mucking with the port directly.  Restore the old colors 
  2080.                   directly based on the Technical Note's recommendation. }
  2081.  
  2082.                 thePort^.fgColor := oldForeColor;
  2083.                 thePort^.bkColor := oldBackColor;
  2084.                 TextMode(oldTxMode);
  2085.                 TextFont(oldTxFont);
  2086.                 TextSize(oldTxSize);
  2087.                 IF gHas32BitQD THEN
  2088.                     PortChanged(ourPort);
  2089.                     
  2090.             END;                      { with myLight^.theCircle^ }
  2091.     DisposeRgn(ourCircleRgn);
  2092. END; { DrawLightParts }
  2093.  
  2094. {$S Main}
  2095. (******************************************************************************
  2096. *
  2097. * private: MyDeviceLoop
  2098. *
  2099. * This is a simple redeclaration of DeviceLoop, using a LONGINT as the last
  2100. * parameter instead of a set of DeviceLoopFlags.  THINK Pascal 4.0.2 may
  2101. * generate incorrect code for passing the DeviceLoopFlags parameter, so we
  2102. * simply make it a LONGINT and use it that way.
  2103. *
  2104. ******************************************************************************)
  2105.  
  2106. PROCEDURE MyDeviceLoop(drawingRgn: RgnHandle; DrawingRoutine: Ptr;
  2107.                        userData: LONGINT; flags: LONGINT);
  2108.     INLINE $ABCA;
  2109.  
  2110. {$S Main}
  2111. (******************************************************************************
  2112. *
  2113. * Public: DrawLight
  2114. *
  2115. * DrawLight draws the light specified by LightConditions.  If DeviceLoop
  2116. * is available, it calls the trap.  If DeviceLoop isn't present but
  2117. * color QuickDraw is, it calls DeviceLoopSim.  If neither is present, or
  2118. * if we're printing, it calls DrawLightParts directly with a depth of zero.
  2119. *
  2120. ******************************************************************************)
  2121.  
  2122. PROCEDURE DrawLight(myLight: LightConditions);
  2123.  
  2124. VAR
  2125.     drawingRgn: RgnHandle;            { Region of area to draw }
  2126.  
  2127. BEGIN
  2128.     drawingRgn := NewRgn;
  2129.     RectRgn(drawingRgn, myLight.theCircle^.circleRect);
  2130.                                     { tell our routines where to draw }
  2131.  
  2132.     IF (gHasDeviceLoop AND NOT myLight.arePrinting) THEN
  2133.         MyDeviceLoop(drawingRgn, @DrawLightParts, LONGINT(@myLight), 0)
  2134.     ELSE IF (gHasColorQD AND NOT myLight.arePrinting) THEN
  2135.         DeviceLoopSim(drawingRgn, @DrawLightParts, LONGINT(@myLight), 0)
  2136.     ELSE
  2137.         DrawLightParts(0, 0, NIL, @myLight); { depth of zero is special, no
  2138.                                               flags, no device }
  2139.  
  2140.     DisposeRgn(drawingRgn);            { dispose of the Region we made }
  2141.  
  2142. END; { DrawLight }
  2143.  
  2144. {$S Main}
  2145. (******************************************************************************
  2146. *
  2147. * Public: DrawDocument
  2148. *
  2149. * This routine draws a document into a given GrafPort.  If printing is TRUE,
  2150. * we assume we're printing and don't erase the portRect first.  If isActive
  2151. * is TRUE, we draw as if this is an active window.  Both flags are also
  2152. * passed to DrawLight so the lights get drawn the right way.
  2153. *
  2154. * We don't erase when printing because it's not necessary, and on some
  2155. * printers it's a slow operation.
  2156. ******************************************************************************)
  2157.  
  2158. PROCEDURE DrawDocument(theDoc: DocumentPtr; drawingPort: GrafPtr; printing,
  2159.                        isActive: BOOLEAN);
  2160.  
  2161. VAR
  2162.     counter: INTEGER;                { loop counter variable }
  2163.     theLight: LightConditions;        { state of each light to draw }
  2164.  
  2165. BEGIN
  2166.     SetPort(drawingPort);            { use the specified port }
  2167.     IF NOT printing THEN
  2168.         EraseRect(drawingPort^.portRect);
  2169.     FOR counter := 1 TO theDoc^.numCircles DO
  2170.         BEGIN
  2171.             theLight.itsOn := (counter = theDoc^.activeCircle); { is it on? }
  2172.             theLight.itsActive := isActive;                        { is active? }
  2173.             theLight.theCircle := @theDoc^.circleArray[counter];{ CircleRec? }
  2174.             theLight.arePrinting := printing;                    { printing? }
  2175.             DrawLight(theLight);
  2176.         END;
  2177. END; { DrawDocument }
  2178.  
  2179. {$S Main}
  2180. (******************************************************************************
  2181. *
  2182. * Public: DrawWindow
  2183. *
  2184. * This is a lot like DrawDocument, except it takes a window for those routines
  2185. * (like those in Sample.p that mainly deal with windows) who don't know our
  2186. * document structure.  If the drawingPort is NIL, we draw to the window
  2187. * passed, otherwise we draw to the second port.  We can therefore redirect
  2188. * if we have a window pointer (in this routine) or if we only have a document
  2189. * pointer (in DrawDocument).
  2190. *
  2191. ******************************************************************************)
  2192.  
  2193. PROCEDURE DrawWindow(window: WindowPtr; drawingPort: GrafPtr; printing: BOOLEAN;
  2194.                      isActive: BOOLEAN);
  2195.  
  2196. VAR
  2197.     ourPort: GrafPtr;            { the port to draw into }
  2198.  
  2199. BEGIN
  2200.     IF drawingPort = NIL THEN
  2201.         ourPort := window
  2202.     ELSE
  2203.         ourPort := drawingPort;
  2204.  
  2205.     DrawDocument(DocumentPtr(GetWRefCon(window)), ourPort, printing, isActive);
  2206.  
  2207. END; { DrawWindow }
  2208.  
  2209. {$S Main}
  2210. (******************************************************************************
  2211. *
  2212. * Public: MakeDocumentPicture
  2213. *
  2214. * This routine draws a document into a QuickDraw PICT and returns the Handle
  2215. * to it.  We use this in printing, and currently if someone selects "copy"
  2216. * from the "Edit" menu.  We open a new GrafPort or CGrafPort, set to it,
  2217. * open a Picture, draw the document and close it all down.
  2218. *
  2219. ******************************************************************************)
  2220.  
  2221. FUNCTION MakeDocumentPicture(theDoc: DocumentPtr): PicHandle;
  2222.  
  2223. VAR
  2224.     myPort: GrafPort;            { the actual port we'll use }
  2225.     theSize: Point;                { the size of the document }
  2226.  
  2227. BEGIN
  2228.     IF gHasColorQD THEN
  2229.         OpenCPort(CGrafPtr(@myPort))    { use color if we can }
  2230.     ELSE
  2231.         OpenPort(@myPort);                { but life goes on if we can't }
  2232.  
  2233.     SetPort(@myPort);
  2234.     GetDocumentDrawingSize(theDoc, theSize);
  2235.     PortSize(theSize.h, theSize.v);
  2236.  
  2237.     MakeDocumentPicture := OpenPicture(myPort.portRect);
  2238.     DrawDocument(theDoc, @myPort, TRUE, TRUE); { pretend we're printing to
  2239.                                                  avoid DeviceLoop }
  2240.     ClosePicture;
  2241.     ClosePort(@myPort);
  2242. END; { MakeDocumentPicture }
  2243.  
  2244. {$S Main}
  2245. (******************************************************************************
  2246. *
  2247. * private: FindContentCircle
  2248. *
  2249. * We use this routine to tell us which circle, if any, a given Point is over
  2250. * in a window.  We return zero if the Point is not inside a circle's rectangle.
  2251. * We use this for hit-testing, so clicking in a circle can turn it "on."
  2252. *
  2253. * The Point must be in global coordinates, like a mouse position in an
  2254. * Event record.
  2255. *
  2256. ******************************************************************************)
  2257.  
  2258. FUNCTION FindContentCircle(window: WindowPtr; where: Point): INTEGER;
  2259.  
  2260. VAR
  2261.     theDoc: DocumentPtr;                { the document for the window }
  2262.     counter: INTEGER;                    { loop variable counter } 
  2263.  
  2264. BEGIN
  2265.     FindContentCircle := 0;                { assume no circle found }
  2266.     SetPort(window);
  2267.     theDoc := DocumentPtr(GetWRefCon(window));
  2268.     GlobalToLocal(where);                { change to local coordinates }
  2269.     FOR counter := 1 TO theDoc^.numCircles DO
  2270.         BEGIN
  2271.             IF PtInRect(where, theDoc^.circleArray[counter].circleRect) THEN
  2272.                 FindContentCircle := counter;
  2273.         END;
  2274. END; { FindContentCircle }
  2275.  
  2276. {$S Main}
  2277. (******************************************************************************
  2278. *
  2279. * Public: ChangeCircleOptions
  2280. *
  2281. * This routine make sure that kDialogMemorySize bytes are available and,
  2282. * if they are, conducts the "Modify Circle" dialog.  We do this here because
  2283. * the routine is in another segment, and if the segment doesn't have enough
  2284. * memory to load, we go boom.
  2285. ******************************************************************************)
  2286.  
  2287. FUNCTION ChangeCircleOptions(VAR circle: CircleRec): BOOLEAN;
  2288.  
  2289. VAR
  2290.     myHandle: Handle;                    { the Handle to test memory with }
  2291.  
  2292. BEGIN
  2293.     ChangeCircleOptions := FALSE;
  2294.     myHandle := NewHandle(kDialogMemorySize);
  2295.     DisposeHandle(myHandle);
  2296.     IF myHandle = NIL THEN
  2297.         AlertUser(rNoMemoryForOperation)
  2298.     ELSE IF OKToInteract THEN
  2299.         ChangeCircleOptions := DoCircleOptions(circle);
  2300. END; { ChangeCircleOptions }
  2301.  
  2302. {$S Main}
  2303. (******************************************************************************
  2304. *
  2305. * Public: DoContentClick
  2306. *
  2307. * Sample.p calls DoContentClick when a mouse-down even occurs in the content
  2308. * of a window.  If we had things other than circles, we might want to call
  2309. * FindControl, TEClick, etc. to further process this click.
  2310. *
  2311. * We call FindContentCircle to figure out which circle they clicked in, if
  2312. * any.  If they did, we make that circle active.  If it's the same
  2313. * window they clicked in last time, and the same rectangle, and no more
  2314. * than GetDblTime ticks have elapsed since the click, this is a double-click,
  2315. * so we call ChangeCircleOptions to modify the circle, invalidating the
  2316. * circle if the user accepted any changes.
  2317. *
  2318. * Then we store the current tick count, window pointer and rectangle number
  2319. * in global variables so we can watch for a double-click next time.
  2320. *
  2321. ******************************************************************************)
  2322.  
  2323. PROCEDURE DoContentClick(window: WindowPtr; event: EventRecord);
  2324.  
  2325. {This is called when a mouse-down event occurs in the content of a window.}
  2326. { Other applications might want to call FindControl, TEClick, etc., to}
  2327. { further process the click.}
  2328.  
  2329. VAR
  2330.     theDoc: DocumentPtr;
  2331.     localPoint: Point;
  2332.     whichRect: INTEGER;
  2333.  
  2334. BEGIN
  2335.     whichRect := FindContentCircle(window, event.where);
  2336.     IF whichRect <> 0 THEN
  2337.         BEGIN
  2338.             theDoc := DocumentPtr(GetWRefCon(window));
  2339.             ChangeActiveCircle(whichRect, theDoc);
  2340.  
  2341.             IF (gLastRectClicked = whichRect) AND (gLastWindowClicked =
  2342.                window) AND (event.when - gLastClickedTime < GetDblTime) THEN
  2343.                 IF ChangeCircleOptions(theDoc^.circleArray[whichRect]) THEN
  2344.                     SetDocumentDirtyFlag(theDoc, kDocumentDirty);
  2345.  
  2346.             gLastClickedTime := event.when;
  2347.             gLastWindowClicked := window;
  2348.             gLastRectClicked := whichRect;
  2349.         END;
  2350.  
  2351. END; { DoContentClick }
  2352.  
  2353. {$S Main}
  2354. (******************************************************************************
  2355. *
  2356. * Public: DoHelp
  2357. *
  2358. * We call DoHelp on a mouse-moved event if balloon help is on and if the
  2359. * mouse is over the frontmost window.
  2360. *
  2361. * If FindContentCircle says the mouse is over one of the circles, we take
  2362. * that circle's rectangle, change it to global coordinates (by calling
  2363. * LocalToGlobal on the topLeft and botRight points), set the "tip" Point
  2364. * to the center of that rectangle and set the 'STR#' index of our help
  2365. * balloon String to either the "active circle" help or the "inactive circle"
  2366. * help.  Then we show the balloon.  The new mouseRgn, returned in the 
  2367. * parameter, is that circle's rectangle.  If the mouse moves outside 
  2368. * that rectangle, we'll get called again.
  2369. *
  2370. * If we're not over a circle, we're still in the window, so we set the
  2371. * Region to the window's content Region minus all the circles' rectangles.
  2372. * in that case, we expect mouseRgn to already be the window's content
  2373. * Region, because that's what the cursorRgn for WaitNextEvent would be
  2374. * if balloon help wasn't on.
  2375. *
  2376. ******************************************************************************)
  2377.  
  2378. PROCEDURE DoHelp(where: Point; mouseRgn: RgnHandle);
  2379.  
  2380. VAR
  2381.     myErr: OSErr;                        { errors from system routines }
  2382.     tip: Point;                            { Point for tip of balloon }
  2383.     theDoc: DocumentPtr;                { the document for the front window }
  2384.     window: WindowPtr;                    { the front window }
  2385.     whichRect,                            { the Rect the mouse is over }
  2386.     rectCount: INTEGER;                    { loop counter variable }
  2387.     newMouseRgn: RgnHandle;                { new Region to calculate }
  2388.     hotRect: Rect;                        { show help while mouse is in this Rect }
  2389.     helpMsg: HMMessageRecord;            { The help message record we use }
  2390.  
  2391. BEGIN
  2392.     window := FrontWindow;
  2393.     theDoc := DocumentPtr(GetWRefCon(window));
  2394.     whichRect := FindContentCircle(window, where);
  2395.     IF whichRect <> 0 THEN
  2396.         BEGIN
  2397.             helpMsg.hmmHelpType := khmmStringRes;
  2398.             helpMsg.hmmStringRes.hmmResID := rBalloonHelpStringID;
  2399.  
  2400.             hotRect := theDoc^.circleArray[whichRect].circleRect;
  2401.             LocalToGlobal(hotRect.topLeft);
  2402.             LocalToGlobal(hotRect.botRight);
  2403.  
  2404.             WITH hotRect DO
  2405.                 SetPt(tip, ((right - left) DIV 2) + left, ((bottom - top) DIV
  2406.                       2) + top);
  2407.  
  2408.             IF whichRect = theDoc^.activeCircle THEN
  2409.                 helpMsg.hmmStringRes.hmmIndex := kActiveCircleBalloonString
  2410.             ELSE
  2411.                 helpMsg.hmmStringRes.hmmIndex := kInactiveCircleBalloonString;
  2412.  
  2413.             myErr := HMShowBalloon(helpMsg, tip, @hotRect, NIL, 0, 0, 0);
  2414.  
  2415.             { return the mouseRgn as a copy of the hotRect }
  2416.             
  2417.             RectRgn(mouseRgn, hotRect);
  2418.  
  2419.         END
  2420.     ELSE 
  2421.         BEGIN
  2422.             { it's not over any circle, so set the Region to the content Region
  2423.                  minus all rectangles }
  2424.               
  2425.             newMouseRgn := NewRgn; { mouseRgn is already the window's content
  2426.                                        Region }
  2427.             OpenRgn;
  2428.             FOR rectCount := 1 TO theDoc^.numCircles DO
  2429.                 BEGIN
  2430.                     
  2431.                     { we use hotRect here as a temporary variable }
  2432.                     
  2433.                     hotRect := theDoc^.circleArray[rectCount].circleRect;
  2434.                     LocalToGlobal(hotRect.topLeft);
  2435.                     LocalToGlobal(hotRect.botRight);
  2436.                     FrameRect(hotRect); { add it to the Region }
  2437.                 END;
  2438.             CloseRgn(newMouseRgn);
  2439.     
  2440.             { subtract the Region with all the rects from the original Region,
  2441.               and put the result into mouseRgn. }
  2442.             
  2443.             DiffRgn(mouseRgn, newMouseRgn, mouseRgn);
  2444.  
  2445.         END; { whichRect <> 0 }
  2446. END; { DoHelp }
  2447.  
  2448. {$S Main}
  2449. (******************************************************************************
  2450. *
  2451. * Public: DoRevert
  2452. *
  2453. * Sample.p calls DoRevert when the user picks the "Revert..." menu item.
  2454. * We ask the user if he really wants to throw away changes to the document,
  2455. * and if he says "Sure," we do it.  We close the existing file and re-read
  2456. * it from disk, redrawing the whole thing.  We return TRUE if there were
  2457. * no errors in this operation.
  2458. *
  2459. ******************************************************************************)
  2460.  
  2461. FUNCTION DoRevert(theWindow: WindowPtr): BOOLEAN;
  2462.  
  2463. VAR
  2464.     theDoc: DocumentPtr;                { this window's document }
  2465.     success: BOOLEAN;                    { result of GetDocumentFromFile }
  2466.     myErr: OSErr;                        { error from system calls }
  2467.  
  2468. BEGIN
  2469.     myErr := noErr;
  2470.     success := TRUE;                    { assume success }
  2471.     theDoc := DocumentPtr(GetWRefCon(theWindow));
  2472.     ParamText(theDoc^.ourFile.name, '', '', '');
  2473.     IF AskUser(rReallyRevert) THEN
  2474.         BEGIN
  2475.             myErr := FSClose(theDoc^.ourFileRefNum);
  2476.             IF myErr = noErr THEN
  2477.                 BEGIN
  2478.                     success := GetDocumentFromFile(@theDoc^.ourFile,
  2479.                                                    theDoc);
  2480.                     SetPort(theWindow);
  2481.                     InvalRect(theWindow^.portRect);
  2482.                 END
  2483.             ELSE
  2484.                 HandleFileError(myErr, theDoc^.ourFile.name);
  2485.         END;
  2486.         
  2487.     DoRevert := ((myErr = noErr) AND success);
  2488.  
  2489. END; { DoRevert }
  2490.